mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Adds some logic that will generate a `forwardRef` if necessary when automatically fixing an import. PR Close #48898
207 lines
7.8 KiB
TypeScript
207 lines
7.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
|
import ts from 'typescript';
|
|
|
|
import {addElementToArrayLiteral, collectMemberMethods, ensureArrayWithIdentifier, findTightestNode, generateImport, hasImport, nonCollidingImportName, objectPropertyAssignmentForKey, printNode, updateImport, updateObjectValueForKey} from '../src/ts_utils';
|
|
import {LanguageServiceTestEnv, OpenBuffer, Project} from '../testing';
|
|
|
|
describe('TS util', () => {
|
|
describe('collectMemberMethods', () => {
|
|
beforeEach(() => {
|
|
initMockFileSystem('Native');
|
|
});
|
|
|
|
it('gets only methods in class, not getters, setters, or properties', () => {
|
|
const files = {
|
|
'app.ts': `
|
|
export class AppCmp {
|
|
prop!: string;
|
|
get myString(): string {
|
|
return '';
|
|
}
|
|
set myString(v: string) {
|
|
}
|
|
|
|
one() {}
|
|
two() {}
|
|
}`,
|
|
};
|
|
const env = LanguageServiceTestEnv.setup();
|
|
const project = env.addProject('test', files);
|
|
const appFile = project.openFile('app.ts');
|
|
appFile.moveCursorToText('AppC¦mp');
|
|
const memberMethods = getMemberMethodNames(project, appFile);
|
|
expect(memberMethods).toEqual(['one', 'two']);
|
|
});
|
|
|
|
it('gets inherited methods in class', () => {
|
|
const files = {
|
|
'app.ts': `
|
|
export class BaseClass {
|
|
baseMethod() {}
|
|
}
|
|
export class AppCmp extends BaseClass {}`,
|
|
};
|
|
const env = LanguageServiceTestEnv.setup();
|
|
const project = env.addProject('test', files);
|
|
const appFile = project.openFile('app.ts');
|
|
appFile.moveCursorToText('AppC¦mp');
|
|
const memberMethods = getMemberMethodNames(project, appFile);
|
|
expect(memberMethods).toEqual(['baseMethod']);
|
|
});
|
|
|
|
it('does not return duplicates if base method is overridden', () => {
|
|
const files = {
|
|
'app.ts': `
|
|
export class BaseClass {
|
|
baseMethod() {}
|
|
}
|
|
export class AppCmp extends BaseClass {
|
|
baseMethod() {}
|
|
}`,
|
|
};
|
|
const env = LanguageServiceTestEnv.setup();
|
|
const project = env.addProject('test', files);
|
|
const appFile = project.openFile('app.ts');
|
|
appFile.moveCursorToText('AppC¦mp');
|
|
const memberMethods = getMemberMethodNames(project, appFile);
|
|
expect(memberMethods).toEqual(['baseMethod']);
|
|
});
|
|
|
|
function getMemberMethodNames(project: Project, file: OpenBuffer): string[] {
|
|
const sf = project.getSourceFile('app.ts')!;
|
|
const node = findTightestNode(sf, file.cursor)!;
|
|
expect(ts.isClassDeclaration(node.parent)).toBe(true);
|
|
return collectMemberMethods(node.parent as ts.ClassDeclaration, project.getTypeChecker())
|
|
.map(m => m.name.getText())
|
|
.sort();
|
|
}
|
|
});
|
|
|
|
describe('AST method', () => {
|
|
let printer: ts.Printer;
|
|
let sourceFile: ts.SourceFile;
|
|
|
|
function print(node: ts.Node): string {
|
|
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
}
|
|
|
|
beforeAll(() => {
|
|
printer = ts.createPrinter();
|
|
sourceFile =
|
|
ts.createSourceFile('placeholder.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS);
|
|
});
|
|
|
|
describe('addElementToArrayLiteral', () => {
|
|
it('transforms an empty array literal expression', () => {
|
|
const oldArr = ts.factory.createArrayLiteralExpression([], false);
|
|
const newArr = addElementToArrayLiteral(oldArr, ts.factory.createStringLiteral('a'));
|
|
expect(print(newArr)).toEqual('["a"]');
|
|
});
|
|
|
|
it('transforms an existing array literal expression', () => {
|
|
const oldArr =
|
|
ts.factory.createArrayLiteralExpression([ts.factory.createStringLiteral('a')], false);
|
|
const newArr = addElementToArrayLiteral(oldArr, ts.factory.createStringLiteral('b'));
|
|
expect(print(newArr)).toEqual('["a", "b"]');
|
|
});
|
|
});
|
|
|
|
describe('objectPropertyAssignmentForKey', () => {
|
|
let oldObj: ts.ObjectLiteralExpression;
|
|
|
|
beforeEach(() => {
|
|
oldObj = ts.factory.createObjectLiteralExpression(
|
|
[ts.factory.createPropertyAssignment(
|
|
ts.factory.createIdentifier('foo'), ts.factory.createStringLiteral('bar'))],
|
|
false);
|
|
});
|
|
|
|
it('returns null when no property exists', () => {
|
|
const prop = objectPropertyAssignmentForKey(oldObj, 'oops');
|
|
expect(prop).toBeNull();
|
|
});
|
|
|
|
it('returns the requested property assignment', () => {
|
|
const prop = objectPropertyAssignmentForKey(oldObj, 'foo');
|
|
expect(print(prop!)).toEqual('foo: "bar"');
|
|
});
|
|
});
|
|
|
|
describe('updateObjectValueForKey', () => {
|
|
let oldObj: ts.ObjectLiteralExpression;
|
|
|
|
const valueAppenderFn = (oldValue?: ts.Expression) => {
|
|
if (!oldValue) return ts.factory.createStringLiteral('baz');
|
|
if (!ts.isStringLiteral(oldValue)) return oldValue;
|
|
return ts.factory.createStringLiteral(oldValue.text + 'baz');
|
|
};
|
|
|
|
beforeEach(() => {
|
|
oldObj = ts.factory.createObjectLiteralExpression(
|
|
[ts.factory.createPropertyAssignment(
|
|
ts.factory.createIdentifier('foo'), ts.factory.createStringLiteral('bar'))],
|
|
false);
|
|
});
|
|
|
|
it('creates a non-existant property', () => {
|
|
const obj = updateObjectValueForKey(oldObj, 'newKey', valueAppenderFn);
|
|
expect(print(obj)).toBe('{ foo: "bar", newKey: "baz" }');
|
|
});
|
|
|
|
it('updates an existing property', () => {
|
|
const obj = updateObjectValueForKey(oldObj, 'foo', valueAppenderFn);
|
|
expect(print(obj)).toBe('{ foo: "barbaz" }');
|
|
});
|
|
});
|
|
|
|
it('addElementToArrayLiteral', () => {
|
|
let arr = ensureArrayWithIdentifier('foo', ts.factory.createIdentifier('foo'));
|
|
arr = addElementToArrayLiteral(arr!, ts.factory.createIdentifier('bar'));
|
|
expect(print(arr)).toEqual('[foo, bar]');
|
|
});
|
|
|
|
it('ensureArrayWithIdentifier', () => {
|
|
let arr = ensureArrayWithIdentifier('foo', ts.factory.createIdentifier('foo'));
|
|
expect(print(arr!)).toEqual('[foo]');
|
|
arr = ensureArrayWithIdentifier('bar', ts.factory.createIdentifier('bar'), arr!);
|
|
expect(print(arr!)).toEqual('[foo, bar]');
|
|
arr = ensureArrayWithIdentifier('bar', ts.factory.createIdentifier('bar'), arr!);
|
|
expect(arr).toEqual(null);
|
|
});
|
|
|
|
it('generateImport', () => {
|
|
let imp = generateImport('Foo', null, './foo');
|
|
expect(print(imp)).toEqual(`import { Foo } from "./foo";`);
|
|
imp = generateImport('Foo', 'Bar', './foo');
|
|
expect(print(imp)).toEqual(`import { Bar as Foo } from "./foo";`);
|
|
});
|
|
|
|
it('updateImport', () => {
|
|
let imp = generateImport('Foo', null, './foo');
|
|
let namedImp = updateImport(imp.importClause!.namedBindings! as ts.NamedImports, 'Bar', null);
|
|
expect(print(namedImp)).toEqual(`{ Foo, Bar }`);
|
|
namedImp = updateImport(imp.importClause!.namedBindings! as ts.NamedImports, 'Foo_2', 'Foo');
|
|
expect(print(namedImp)).toEqual(`{ Foo, Foo as Foo_2 }`);
|
|
namedImp = updateImport(imp.importClause!.namedBindings! as ts.NamedImports, 'Bar', 'Bar');
|
|
expect(print(namedImp)).toEqual(`{ Foo, Bar }`);
|
|
});
|
|
|
|
it('nonCollidingImportName', () => {
|
|
let imps = [
|
|
generateImport('Foo', null, './foo'),
|
|
generateImport('Bar', 'ExternalBar', './bar'),
|
|
];
|
|
expect(nonCollidingImportName(imps, 'Other')).toEqual('Other');
|
|
expect(nonCollidingImportName(imps, 'Foo')).toEqual('Foo_1');
|
|
expect(nonCollidingImportName(imps, 'ExternalBar')).toEqual('ExternalBar');
|
|
});
|
|
});
|
|
});
|