feat(ivy): a generic visitor which allows prefixing nodes for ngtsc (#24230)
This adds ngtsc/util/src/visitor, a utility for visiting TS ASTs that can add synthetic nodes immediately prior to certain types of nodes (e.g. class declarations). It's useful to lift definitions that need to be referenced repeatedly in generated code outside of the class that defines them. PR Close #24230
This commit is contained in:

committed by
Miško Hevery

parent
f781f741ea
commit
ca79e11bfa
15
packages/compiler-cli/src/ngtsc/testing/BUILD.bazel
Normal file
15
packages/compiler-cli/src/ngtsc/testing/BUILD.bazel
Normal file
@ -0,0 +1,15 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
ts_library(
|
||||
name = "testing",
|
||||
testonly = 1,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
],
|
||||
)
|
130
packages/compiler-cli/src/ngtsc/testing/in_memory_typescript.ts
Normal file
130
packages/compiler-cli/src/ngtsc/testing/in_memory_typescript.ts
Normal file
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export function makeProgram(files: {name: string, contents: string}[]):
|
||||
{program: ts.Program, host: ts.CompilerHost} {
|
||||
const host = new InMemoryHost();
|
||||
files.forEach(file => host.writeFile(file.name, file.contents));
|
||||
|
||||
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
|
||||
const program = ts.createProgram(rootNames, {noLib: true, experimentalDecorators: true}, host);
|
||||
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
|
||||
if (diags.length > 0) {
|
||||
throw new Error(
|
||||
`Typescript diagnostics failed! ${diags.map(diag => diag.messageText).join(', ')}`);
|
||||
}
|
||||
return {program, host};
|
||||
}
|
||||
|
||||
export class InMemoryHost implements ts.CompilerHost {
|
||||
private fileSystem = new Map<string, string>();
|
||||
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||
const contents = this.fileSystem.get(this.getCanonicalFileName(fileName));
|
||||
if (contents === undefined) {
|
||||
onError && onError(`File does not exist: ${this.getCanonicalFileName(fileName)})`);
|
||||
return undefined;
|
||||
}
|
||||
return ts.createSourceFile(fileName, contents, languageVersion, undefined, ts.ScriptKind.TS);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return '/lib.d.ts'; }
|
||||
|
||||
writeFile(
|
||||
fileName: string, data: string, writeByteOrderMark?: boolean,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
sourceFiles?: ReadonlyArray<ts.SourceFile>): void {
|
||||
this.fileSystem.set(this.getCanonicalFileName(fileName), data);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return '/'; }
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
const fullDir = this.getCanonicalFileName(dir) + '/';
|
||||
const dirSet = new Set(Array
|
||||
// Look at all paths known to the host.
|
||||
.from(this.fileSystem.keys())
|
||||
// Filter out those that aren't under the requested directory.
|
||||
.filter(candidate => candidate.startsWith(fullDir))
|
||||
// Relativize the rest by the requested directory.
|
||||
.map(candidate => candidate.substr(fullDir.length))
|
||||
// What's left are dir/.../file.txt entries, and file.txt entries.
|
||||
// Get the dirname, which
|
||||
// yields '.' for the latter and dir/... for the former.
|
||||
.map(candidate => path.dirname(candidate))
|
||||
// Filter out the '.' entries, which were files.
|
||||
.filter(candidate => candidate !== '.')
|
||||
// Finally, split on / and grab the first entry.
|
||||
.map(candidate => candidate.split('/', 1)[0]));
|
||||
|
||||
// Get the resulting values out of the Set.
|
||||
return Array.from(dirSet);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return path.posix.normalize(`${this.getCurrentDirectory()}/${fileName}`);
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean { return true; }
|
||||
|
||||
getNewLine(): string { return '\n'; }
|
||||
|
||||
fileExists(fileName: string): boolean { return this.fileSystem.has(fileName); }
|
||||
|
||||
readFile(fileName: string): string|undefined { return this.fileSystem.get(fileName); }
|
||||
}
|
||||
|
||||
function bindingNameEquals(node: ts.BindingName, name: string): boolean {
|
||||
if (ts.isIdentifier(node)) {
|
||||
return node.text === name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getDeclaration<T extends ts.Declaration>(
|
||||
program: ts.Program, fileName: string, name: string, assert: (value: any) => value is T): T {
|
||||
const sf = program.getSourceFile(fileName);
|
||||
if (!sf) {
|
||||
throw new Error(`No such file: ${fileName}`);
|
||||
}
|
||||
|
||||
let chosenDecl: ts.Declaration|null = null;
|
||||
|
||||
sf.statements.forEach(stmt => {
|
||||
if (chosenDecl !== null) {
|
||||
return;
|
||||
} else if (ts.isVariableStatement(stmt)) {
|
||||
stmt.declarationList.declarations.forEach(decl => {
|
||||
if (bindingNameEquals(decl.name, name)) {
|
||||
chosenDecl = decl;
|
||||
}
|
||||
});
|
||||
} else if (ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt)) {
|
||||
if (stmt.name !== undefined && stmt.name.text === name) {
|
||||
chosenDecl = stmt;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chosenDecl = chosenDecl as ts.Declaration | null;
|
||||
|
||||
if (chosenDecl === null) {
|
||||
throw new Error(`No such symbol: ${name} in ${fileName}`);
|
||||
}
|
||||
if (!assert(chosenDecl)) {
|
||||
throw new Error(`Symbol ${name} from ${fileName} is a ${ts.SyntaxKind[chosenDecl.kind]}`);
|
||||
}
|
||||
|
||||
return chosenDecl;
|
||||
}
|
Reference in New Issue
Block a user