feat(compiler-cli): expose ngtsc as a TscPlugin (#28435)
This lets us run ngtsc under the tsc_wrapped custom compiler (Used in Bazel) It also allows others to simply wire ngtsc into an existing typescript compilation binary PR Close #28435
This commit is contained in:
parent
0eef958735
commit
a227c528ca
|
@ -36,7 +36,7 @@
|
||||||
"@angular-devkit/core": "^7.0.4",
|
"@angular-devkit/core": "^7.0.4",
|
||||||
"@angular-devkit/schematics": "^7.3.0-rc.0",
|
"@angular-devkit/schematics": "^7.3.0-rc.0",
|
||||||
"@bazel/karma": "~0.22.1",
|
"@bazel/karma": "~0.22.1",
|
||||||
"@bazel/typescript": "~0.22.1",
|
"@bazel/typescript": "0.22.1-7-g68fed6a",
|
||||||
"@schematics/angular": "^7.0.4",
|
"@schematics/angular": "^7.0.4",
|
||||||
"@types/angular": "^1.6.47",
|
"@types/angular": "^1.6.47",
|
||||||
"@types/chokidar": "1.7.3",
|
"@types/chokidar": "1.7.3",
|
||||||
|
|
|
@ -24,3 +24,4 @@ export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'
|
||||||
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
|
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
|
||||||
|
|
||||||
export {ngToTsDiagnostic} from './src/transformers/util';
|
export {ngToTsDiagnostic} from './src/transformers/util';
|
||||||
|
export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
|
||||||
|
|
|
@ -36,6 +36,8 @@ const requiredNodeModules = {
|
||||||
'@angular/platform-server':
|
'@angular/platform-server':
|
||||||
resolveNpmTreeArtifact('angular/packages/platform-server/npm_package'),
|
resolveNpmTreeArtifact('angular/packages/platform-server/npm_package'),
|
||||||
'@angular/router': resolveNpmTreeArtifact('angular/packages/router/npm_package'),
|
'@angular/router': resolveNpmTreeArtifact('angular/packages/router/npm_package'),
|
||||||
|
// Note, @bazel/typescript does not appear here because it's not listed as a dependency of
|
||||||
|
// @angular/compiler-cli
|
||||||
'@types/jasmine': resolveNpmTreeArtifact('ngdeps/node_modules/@types/jasmine'),
|
'@types/jasmine': resolveNpmTreeArtifact('ngdeps/node_modules/@types/jasmine'),
|
||||||
'@types/node': resolveNpmTreeArtifact('ngdeps/node_modules/@types/node'),
|
'@types/node': resolveNpmTreeArtifact('ngdeps/node_modules/@types/node'),
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* @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 {PluginCompilerHost} from '@bazel/typescript/tsc_wrapped/plugin_api';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of the TypeScript compiler host that supports files added to the Program which
|
||||||
|
* were never on disk.
|
||||||
|
*
|
||||||
|
* This is used for backwards-compatibility with the ViewEngine compiler, which used ngsummary
|
||||||
|
* and ngfactory files as inputs to the program. We call these inputs "synthetic".
|
||||||
|
*
|
||||||
|
* They need to be program inputs because user code may import from these generated files.
|
||||||
|
*
|
||||||
|
* TODO(alxhub): remove this after all ng_module users have migrated to Ivy
|
||||||
|
*/
|
||||||
|
export class SyntheticFilesCompilerHost implements PluginCompilerHost {
|
||||||
|
/**
|
||||||
|
* SourceFiles which are added to the program but which never existed on disk.
|
||||||
|
*/
|
||||||
|
syntheticFiles = new Map<string, ts.SourceFile>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private rootFiles: string[], private delegate: ts.CompilerHost,
|
||||||
|
generatedFiles: (rootFiles: string[]) => {
|
||||||
|
[fileName: string]: (host: ts.CompilerHost) => ts.SourceFile | undefined
|
||||||
|
}, ) {
|
||||||
|
// Allow ngtsc to contribute in-memory synthetic files, which will be loaded
|
||||||
|
// as if they existed on disk as action inputs.
|
||||||
|
const angularGeneratedFiles = generatedFiles !(rootFiles);
|
||||||
|
for (const f of Object.keys(angularGeneratedFiles)) {
|
||||||
|
const generator = angularGeneratedFiles[f];
|
||||||
|
const generated = generator(delegate);
|
||||||
|
if (generated) {
|
||||||
|
this.syntheticFiles.set(generated.fileName, generated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExists(filePath: string): boolean {
|
||||||
|
if (this.syntheticFiles.has(filePath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.delegate.fileExists(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loads a source file from in-memory map, or delegates. */
|
||||||
|
getSourceFile(
|
||||||
|
fileName: string, languageVersion: ts.ScriptTarget,
|
||||||
|
onError?: (message: string) => void): ts.SourceFile|undefined {
|
||||||
|
const syntheticFile = this.syntheticFiles.get(fileName);
|
||||||
|
if (syntheticFile) {
|
||||||
|
return syntheticFile !;
|
||||||
|
}
|
||||||
|
return this.delegate.getSourceFile(fileName, languageVersion, onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
get inputFiles() { return [...this.rootFiles, ...Array.from(this.syntheticFiles.keys())]; }
|
||||||
|
|
||||||
|
fileNameToModuleId(fileName: string) {
|
||||||
|
return fileName; // TODO: Ivy logic. don't forget that the delegate has the google3 logic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegate everything else to the original compiler host.
|
||||||
|
|
||||||
|
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||||
|
return this.delegate.getDefaultLibFileName(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(
|
||||||
|
fileName: string, content: string, writeByteOrderMark: boolean,
|
||||||
|
onError: ((message: string) => void)|undefined,
|
||||||
|
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined): void {
|
||||||
|
this.delegate.writeFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCanonicalFileName(path: string) { return this.delegate.getCanonicalFileName(path); }
|
||||||
|
|
||||||
|
getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); }
|
||||||
|
|
||||||
|
useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); }
|
||||||
|
|
||||||
|
getNewLine(): string { return this.delegate.getNewLine(); }
|
||||||
|
|
||||||
|
getDirectories(path: string) { return this.delegate.getDirectories(path); }
|
||||||
|
|
||||||
|
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
|
||||||
|
|
||||||
|
trace(s: string): void { console.error(s); }
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/**
|
||||||
|
* @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 {PluginCompilerHost, TscPlugin} from '@bazel/typescript/tsc_wrapped/plugin_api';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {SyntheticFilesCompilerHost} from './synthetic_files_compiler_host';
|
||||||
|
|
||||||
|
// Copied from tsc_wrapped/plugin_api.ts to avoid a runtime dependency on that package
|
||||||
|
function createProxy<T>(delegate: T): T {
|
||||||
|
const proxy = Object.create(null);
|
||||||
|
for (const k of Object.keys(delegate)) {
|
||||||
|
proxy[k] = function() { return (delegate as any)[k].apply(delegate, arguments); };
|
||||||
|
}
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NgTscPlugin implements TscPlugin {
|
||||||
|
constructor(private angularCompilerOptions: unknown) {}
|
||||||
|
|
||||||
|
wrap(program: ts.Program, config: {}, host: ts.CompilerHost) {
|
||||||
|
const proxy = createProxy(program);
|
||||||
|
proxy.getSemanticDiagnostics = (sourceFile: ts.SourceFile) => {
|
||||||
|
const result: ts.Diagnostic[] = [...program.getSemanticDiagnostics(sourceFile)];
|
||||||
|
|
||||||
|
// For demo purposes, trigger a diagnostic when the sourcefile has a magic string
|
||||||
|
if (sourceFile.text.indexOf('diag') >= 0) {
|
||||||
|
const fake: ts.Diagnostic = {
|
||||||
|
file: sourceFile,
|
||||||
|
start: 0,
|
||||||
|
length: 3,
|
||||||
|
messageText: 'Example Angular Compiler Diagnostic',
|
||||||
|
category: ts.DiagnosticCategory.Error,
|
||||||
|
code: 12345,
|
||||||
|
// source is the name of the plugin.
|
||||||
|
source: 'Angular',
|
||||||
|
};
|
||||||
|
result.push(fake);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
createTransformers(host: PluginCompilerHost) {
|
||||||
|
const afterDeclarations: Array<ts.TransformerFactory<ts.SourceFile|ts.Bundle>> =
|
||||||
|
[(context: ts.TransformationContext) => (sf: ts.SourceFile | ts.Bundle) => {
|
||||||
|
const visitor = (node: ts.Node): ts.Node => {
|
||||||
|
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
|
||||||
|
const clz = node as ts.ClassDeclaration;
|
||||||
|
// For demo purposes, transform the class name in the .d.ts output
|
||||||
|
return ts.updateClassDeclaration(
|
||||||
|
clz, clz.decorators, node.modifiers, ts.createIdentifier('NEWNAME'),
|
||||||
|
clz.typeParameters, clz.heritageClauses, clz.members);
|
||||||
|
}
|
||||||
|
return ts.visitEachChild(node, visitor, context);
|
||||||
|
};
|
||||||
|
return visitor(sf) as ts.SourceFile;
|
||||||
|
}];
|
||||||
|
return {afterDeclarations};
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapHost(inputFiles: string[], compilerHost: ts.CompilerHost) {
|
||||||
|
return new SyntheticFilesCompilerHost(inputFiles, compilerHost, this.generatedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedFiles(rootFiles: string[]) {
|
||||||
|
return {
|
||||||
|
'file-1.ts': (host: ts.CompilerHost) =>
|
||||||
|
ts.createSourceFile('file-1.ts', 'contents', ts.ScriptTarget.ES5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -160,10 +160,10 @@
|
||||||
semver "5.6.0"
|
semver "5.6.0"
|
||||||
tmp "0.0.33"
|
tmp "0.0.33"
|
||||||
|
|
||||||
"@bazel/typescript@~0.22.1":
|
"@bazel/typescript@0.22.1-7-g68fed6a":
|
||||||
version "0.22.1"
|
version "0.22.1-7-g68fed6a"
|
||||||
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-0.22.1.tgz#b52c00e8560019e2f9d273d45c04785e0ec9d9bd"
|
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-0.22.1-7-g68fed6a.tgz#d6340fbfcfdeb3893e66ec8a435b850cfa83d60f"
|
||||||
integrity sha512-88DaCCnNg8rPlKP0eAQEZuoiJkEPeiItpUS3oBR1sFQNBRJb56D25ahK8+N6LJk4qaH+ZQ1/AHOPDhfEEWvDzA==
|
integrity sha512-EBPxbge/RBas7zSLvCjUgtpIVdjU+AgZ6YyCMTaYcn4hzD1eYQUpGHT+fa9/icHvk/84wWGXCFRb1+uZsBofVA==
|
||||||
dependencies:
|
dependencies:
|
||||||
protobufjs "5.0.3"
|
protobufjs "5.0.3"
|
||||||
semver "5.6.0"
|
semver "5.6.0"
|
||||||
|
|
Loading…
Reference in New Issue