Skip to content

Commit

Permalink
feature: Implemented updateClass()
Browse files Browse the repository at this point in the history
  • Loading branch information
ijlee2 committed Apr 23, 2024
1 parent 0121e03 commit 261a40b
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/migration/ember-app/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { CodemodOptions } from '../../types/index.js';
import { createOptions } from '../../utils/steps/create-options.js';
import { updateClasses } from '../../utils/steps/update-classes.js';
import { updateProject } from '../../utils/steps/update-project.js';

export function migrateEmberApp(codemodOptions: CodemodOptions): void {
const options = createOptions(codemodOptions);

updateClasses(['app/**/*.{js,ts}'], options);
updateProject(['app/**/*.{js,ts}'], options);
}
4 changes: 2 additions & 2 deletions src/migration/ember-v1-addon/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { CodemodOptions } from '../../types/index.js';
import { createOptions } from '../../utils/steps/create-options.js';
import { updateClasses } from '../../utils/steps/update-classes.js';
import { updateProject } from '../../utils/steps/update-project.js';

export function migrateEmberV1Addon(codemodOptions: CodemodOptions): void {
const options = createOptions(codemodOptions);

updateClasses(
updateProject(
['addon/**/*.{js,ts}', 'tests/dummy/app/**/*.{js,ts}'],
options,
);
Expand Down
4 changes: 2 additions & 2 deletions src/migration/ember-v2-addon/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { CodemodOptions } from '../../types/index.js';
import { createOptions } from '../../utils/steps/create-options.js';
import { updateClasses } from '../../utils/steps/update-classes.js';
import { updateProject } from '../../utils/steps/update-project.js';

export function migrateEmberV2Addon(codemodOptions: CodemodOptions): void {
const options = createOptions(codemodOptions);

updateClasses(['src/**/*.{js,ts}'], options);
updateProject(['src/**/*.{js,ts}'], options);
}
17 changes: 0 additions & 17 deletions src/utils/steps/update-classes/find-local-name.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/utils/steps/update-classes/rename-imports.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { join } from 'node:path';
import { findFiles } from '@codemod-utils/files';

import { Options } from '../../types/index.js';
import { findLocalName } from './update-classes/find-local-name.js';
import { renameImports } from './update-classes/rename-imports.js';
import { updateClass } from './update-project/update-class.js';

export function updateClasses(src: string[], options: Options): void {
export function updateProject(src: string[], options: Options): void {
const { projectRoot } = options;

const filePaths = findFiles(src, {
Expand All @@ -20,18 +19,7 @@ export function updateClasses(src: string[], options: Options): void {

const isTypeScript = filePath.endsWith('.ts');

const localName = findLocalName(oldFile, {
isTypeScript,
});

if (localName === undefined) {
return;
}

const newFile = renameImports(oldFile, {
isTypeScript,
localName,
});
const newFile = updateClass(oldFile, isTypeScript);

writeFileSync(oldPath, newFile, 'utf8');
});
Expand Down
157 changes: 157 additions & 0 deletions src/utils/steps/update-project/update-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { AST } from '@codemod-utils/ast-javascript';

function isValueImport(
importKind: 'type' | 'typeof' | 'value' | undefined,
): boolean {
return importKind === undefined || importKind === 'value';
}

function updateImportStatement(
file: string,
data: {
isTypeScript: boolean;
},
): {
localName: string | undefined;
newFile: string;
} {
let localName: string | undefined;

const traverse = AST.traverse(data.isTypeScript);

const ast = traverse(file, {
visitImportDeclaration(path) {
const { importKind, source, specifiers } = path.node;

if (!isValueImport(importKind)) {
return false;
}

if (source.type !== 'Literal' && source.type !== 'StringLiteral') {
return false;
}

if (source.value !== '@ember/service' || !Array.isArray(specifiers)) {
return false;
}

path.node.specifiers = specifiers.map((specifier) => {
// @ts-expect-error: 'specifier.importKind' exists
if (!isValueImport(specifier.importKind)) {
return specifier;
}

if (specifier.type !== 'ImportSpecifier') {
return specifier;
}

if (
specifier.imported.name !== 'inject' &&
specifier.imported.name !== 'service'
) {
return specifier;
}

localName = specifier.local!.name as string;

return AST.builders.importSpecifier(AST.builders.identifier('service'));
});

return false;
},
});

return {
localName,
newFile: AST.print(ast),
};
}

function updateServiceDecorators(
file: string,
data: {
isTypeScript: boolean;
localName: string;
},
): string {
const traverse = AST.traverse(data.isTypeScript);

const ast = traverse(file, {
visitCallExpression(node) {
this.traverse(node);

switch (node.value.callee.type) {
case 'Identifier': {
if (node.value.callee.name === data.localName) {
node.value.callee.name = 'service';
}

break;
}
}

return false;
},

visitClassProperty(node) {
if (
!Array.isArray(node.value.decorators) ||
node.value.decorators.length !== 1
) {
return false;
}

const decorator = node.value.decorators[0];
let isMatch = false;

switch (decorator.expression.type) {
case 'CallExpression': {
if (decorator.expression.callee.name === data.localName) {
decorator.expression.callee.name = 'service';
isMatch = true;
}

break;
}

case 'Identifier': {
if (decorator.expression.name === data.localName) {
decorator.expression.name = 'service';
isMatch = true;
}

break;
}
}

if (isMatch && data.isTypeScript) {
node.value.accessibility = null;
node.value.declare = true;
node.value.definite = null;
node.value.readonly = null;
}

return false;
},
});

return AST.print(ast);
}

export function updateClass(file: string, isTypeScript: boolean): string {
// eslint-disable-next-line prefer-const
let { localName, newFile } = updateImportStatement(file, {
isTypeScript,
});

if (!localName) {
return file;
}

newFile = updateServiceDecorators(newFile, {
isTypeScript,
localName,
});

return newFile;
}

0 comments on commit 261a40b

Please sign in to comment.