mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-19 12:34:32 +08:00
refactor(@schematics/angular): add utility to find top-level identifiers
Adds a utility that will find if a source file has a top-level identifier with a certain name. This will be useful when trying to avoid conflicts in generated imports later on.
This commit is contained in:
parent
90e69badcc
commit
b36effda51
@ -665,3 +665,52 @@ export function addRouteDeclarationToModule(
|
|||||||
|
|
||||||
return new InsertChange(fileToAdd, insertPos, route);
|
return new InsertChange(fileToAdd, insertPos, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Asserts if the specified node is a named declaration (e.g. class, interface). */
|
||||||
|
function isNamedNode(
|
||||||
|
node: ts.Node & { name?: ts.Node },
|
||||||
|
): node is ts.Node & { name: ts.Identifier } {
|
||||||
|
return !!node.name && ts.isIdentifier(node.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a SourceFile has a top-level declaration whose name matches a specific symbol.
|
||||||
|
* Can be used to avoid conflicts when inserting new imports into a file.
|
||||||
|
* @param sourceFile File in which to search.
|
||||||
|
* @param symbolName Name of the symbol to search for.
|
||||||
|
* @param skipModule Path of the module that the symbol may have been imported from. Used to
|
||||||
|
* avoid false positives where the same symbol we're looking for may have been imported.
|
||||||
|
*/
|
||||||
|
export function hasTopLevelIdentifier(
|
||||||
|
sourceFile: ts.SourceFile,
|
||||||
|
symbolName: string,
|
||||||
|
skipModule: string | null = null,
|
||||||
|
): boolean {
|
||||||
|
for (const node of sourceFile.statements) {
|
||||||
|
if (isNamedNode(node) && node.name.text === symbolName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ts.isVariableStatement(node) &&
|
||||||
|
node.declarationList.declarations.some((decl) => {
|
||||||
|
return isNamedNode(decl) && decl.name.text === symbolName;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ts.isImportDeclaration(node) &&
|
||||||
|
ts.isStringLiteralLike(node.moduleSpecifier) &&
|
||||||
|
node.moduleSpecifier.text !== skipModule &&
|
||||||
|
node.importClause?.namedBindings &&
|
||||||
|
ts.isNamedImports(node.importClause.namedBindings) &&
|
||||||
|
node.importClause.namedBindings.elements.some((el) => el.name.text === symbolName)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
addRouteDeclarationToModule,
|
addRouteDeclarationToModule,
|
||||||
addSymbolToNgModuleMetadata,
|
addSymbolToNgModuleMetadata,
|
||||||
findNodes,
|
findNodes,
|
||||||
|
hasTopLevelIdentifier,
|
||||||
insertAfterLastOccurrence,
|
insertAfterLastOccurrence,
|
||||||
insertImport,
|
insertImport,
|
||||||
} from './ast-utils';
|
} from './ast-utils';
|
||||||
@ -776,4 +777,74 @@ describe('ast utils', () => {
|
|||||||
expect(result).toBe(fileContent);
|
expect(result).toBe(fileContent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('hasTopLevelIdentifier', () => {
|
||||||
|
const filePath = './src/foo.ts';
|
||||||
|
|
||||||
|
it('should find top-level class declaration with a specific name', () => {
|
||||||
|
const fileContent = `class FooClass {}`;
|
||||||
|
const source = getTsSource(filePath, fileContent);
|
||||||
|
|
||||||
|
expect(hasTopLevelIdentifier(source, 'FooClass')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'Foo')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find top-level interface declaration with a specific name', () => {
|
||||||
|
const fileContent = `interface FooInterface {}`;
|
||||||
|
const source = getTsSource(filePath, fileContent);
|
||||||
|
|
||||||
|
expect(hasTopLevelIdentifier(source, 'FooInterface')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'Foo')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find top-level variable declaration with a specific name', () => {
|
||||||
|
const fileContent = `
|
||||||
|
const singleVar = 1;
|
||||||
|
|
||||||
|
const fooVar = 1, barVar = 2;
|
||||||
|
`;
|
||||||
|
const source = getTsSource(filePath, fileContent);
|
||||||
|
|
||||||
|
expect(hasTopLevelIdentifier(source, 'singleVar')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'fooVar')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'barVar')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'bar')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find top-level imports with a specific name', () => {
|
||||||
|
const fileContent = `
|
||||||
|
import { FooInterface } from '@foo/interfaces';
|
||||||
|
|
||||||
|
class FooClass implements FooInterface {}
|
||||||
|
`;
|
||||||
|
const source = getTsSource(filePath, fileContent);
|
||||||
|
|
||||||
|
expect(hasTopLevelIdentifier(source, 'FooInterface')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'Foo')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find top-level aliased imports with a specific name', () => {
|
||||||
|
const fileContent = `
|
||||||
|
import { FooInterface as AliasedFooInterface } from '@foo/interfaces';
|
||||||
|
|
||||||
|
class FooClass implements AliasedFooInterface {}
|
||||||
|
`;
|
||||||
|
const source = getTsSource(filePath, fileContent);
|
||||||
|
|
||||||
|
expect(hasTopLevelIdentifier(source, 'AliasedFooInterface')).toBe(true);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'FooInterface')).toBe(false);
|
||||||
|
expect(hasTopLevelIdentifier(source, 'Foo')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to skip imports from a certain module', () => {
|
||||||
|
const fileContent = `
|
||||||
|
import { FooInterface } from '@foo/interfaces';
|
||||||
|
|
||||||
|
class FooClass implements FooInterface {}
|
||||||
|
`;
|
||||||
|
const source = getTsSource(filePath, fileContent);
|
||||||
|
|
||||||
|
expect(hasTopLevelIdentifier(source, 'FooInterface', '@foo/interfaces')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user