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/schematics": "^7.3.0-rc.0",
|
||||
"@bazel/karma": "~0.22.1",
|
||||
"@bazel/typescript": "~0.22.1",
|
||||
"@bazel/typescript": "0.22.1-7-g68fed6a",
|
||||
"@schematics/angular": "^7.0.4",
|
||||
"@types/angular": "^1.6.47",
|
||||
"@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 {ngToTsDiagnostic} from './src/transformers/util';
|
||||
export {NgTscPlugin} from './src/ngtsc/tsc_plugin';
|
||||
|
|
|
@ -36,6 +36,8 @@ const requiredNodeModules = {
|
|||
'@angular/platform-server':
|
||||
resolveNpmTreeArtifact('angular/packages/platform-server/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/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"
|
||||
tmp "0.0.33"
|
||||
|
||||
"@bazel/typescript@~0.22.1":
|
||||
version "0.22.1"
|
||||
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-0.22.1.tgz#b52c00e8560019e2f9d273d45c04785e0ec9d9bd"
|
||||
integrity sha512-88DaCCnNg8rPlKP0eAQEZuoiJkEPeiItpUS3oBR1sFQNBRJb56D25ahK8+N6LJk4qaH+ZQ1/AHOPDhfEEWvDzA==
|
||||
"@bazel/typescript@0.22.1-7-g68fed6a":
|
||||
version "0.22.1-7-g68fed6a"
|
||||
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-0.22.1-7-g68fed6a.tgz#d6340fbfcfdeb3893e66ec8a435b850cfa83d60f"
|
||||
integrity sha512-EBPxbge/RBas7zSLvCjUgtpIVdjU+AgZ6YyCMTaYcn4hzD1eYQUpGHT+fa9/icHvk/84wWGXCFRb1+uZsBofVA==
|
||||
dependencies:
|
||||
protobufjs "5.0.3"
|
||||
semver "5.6.0"
|
||||
|
|
Loading…
Reference in New Issue