mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Initial support for minimal compiler plugin API
This commit is contained in:
+59
-3
@@ -94,18 +94,74 @@ const localize = async () => {
|
||||
const lkgPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics));
|
||||
|
||||
const buildTsc = () => buildProject("src/tsc");
|
||||
|
||||
task("tsc", series(lkgPreBuild, buildTsc));
|
||||
task("tsc").description = "Builds the command-line compiler";
|
||||
|
||||
const cleanTsc = () => cleanProject("src/tsc");
|
||||
cleanTasks.push(cleanTsc);
|
||||
task("clean-tsc", cleanTsc);
|
||||
task("clean-tsc", parallel(cleanTsc));
|
||||
task("clean-tsc").description = "Cleans outputs for the command-line compiler";
|
||||
|
||||
const watchTsc = () => watchProject("src/tsc");
|
||||
task("watch-tsc", series(lkgPreBuild, parallel(watchLib, watchDiagnostics, watchTsc)));
|
||||
task("watch-tsc").description = "Watch for changes and rebuild the command-line compiler only.";
|
||||
|
||||
const buildApi = (() => {
|
||||
const flattenCompiler = async () => flatten("src/compiler/tsconfig.json", "built/local/api.tsconfig.json", {
|
||||
compilerOptions: {
|
||||
removeComments: false,
|
||||
stripInternal: true,
|
||||
emitDeclarationOnly: true,
|
||||
declaration: true,
|
||||
declarationMap: false,
|
||||
outFile: "api.out.js"
|
||||
}
|
||||
});
|
||||
|
||||
const buildApiOut = () => buildProject("built/local/api.tsconfig.json");
|
||||
|
||||
const generateApiDts = () => src("built/local/api.out.d.ts", { base: "built/local" })
|
||||
.pipe(newer("built/local/api.d.ts"))
|
||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||
.pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4")))
|
||||
.pipe(transform(content => `${content}\nexport = typeof ts;\n`))
|
||||
.pipe(prependFile(copyright))
|
||||
.pipe(rename("api.d.ts"))
|
||||
.pipe(dest("built/local"));
|
||||
|
||||
const generateApiJs = () => src(copyright)
|
||||
.pipe(newer("built/local/api.js"))
|
||||
.pipe(transform(content => `${content}// NOTE: 'api' only exports types used by plugins. You can access the actual api from the context\n// provided to a plugin hook.\nmodule.exports = {};`))
|
||||
.pipe(rename("api.js"))
|
||||
.pipe(dest("built/local"));
|
||||
|
||||
return series(flattenCompiler, buildApiOut, generateApiDts, generateApiJs);
|
||||
})();
|
||||
task("api", series(lkgPreBuild, buildApi));
|
||||
task("api").description = "Build the compiler plugin API";
|
||||
|
||||
const cleanApi = async () => {
|
||||
if (fs.existsSync("built/local/api.tsconfig.json")) {
|
||||
await cleanProject("built/local/api.tsconfig.json");
|
||||
}
|
||||
await del([
|
||||
"built/local/api.tsconfig.json",
|
||||
"built/local/api.out.js",
|
||||
"built/local/api.out.d.ts",
|
||||
"built/local/tsc.d.ts"
|
||||
]);
|
||||
}
|
||||
cleanTasks.push(cleanApi);
|
||||
task("clean-api", cleanApi);
|
||||
task("clean-api").description = "Clean the outputs of the compiler plugin API.";
|
||||
|
||||
const watchApi = () => watch([
|
||||
"src/compiler/tsconfig.json",
|
||||
"src/compiler/**/*.ts",
|
||||
], series(lkgPreBuild, buildApi));
|
||||
task("watch-api", series(lkgPreBuild, parallel(watchDiagnostics, watchApi)));
|
||||
|
||||
// Pre-build steps when targeting the built/local compiler.
|
||||
const localPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics, buildTsc));
|
||||
|
||||
@@ -377,13 +433,13 @@ task("other-outputs").description = "Builds miscelaneous scripts and documents d
|
||||
|
||||
const buildFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("build")); };
|
||||
const buildFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("build")); };
|
||||
task("local", series(buildFoldStart, preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs), buildFoldEnd));
|
||||
task("local", series(buildFoldStart, preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, buildApi), buildFoldEnd));
|
||||
task("local").description = "Builds the full compiler and services";
|
||||
task("local").flags = {
|
||||
" --built": "Compile using the built version of the compiler."
|
||||
};
|
||||
|
||||
task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl)));
|
||||
task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl, watchApi)));
|
||||
task("watch-local").description = "Watches for changes to projects in src/ (but does not execute tests).";
|
||||
task("watch-local").flags = {
|
||||
" --built": "Compile using the built version of the compiler."
|
||||
|
||||
@@ -2022,7 +2022,8 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function isNullOrUndefined(x: any): x is null | undefined {
|
||||
/*@internal*/
|
||||
export function isNullOrUndefined(x: any): x is null | undefined {
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
@@ -2063,6 +2064,7 @@ namespace ts {
|
||||
setConfigFileInOptions(options, sourceFile);
|
||||
let projectReferences: ProjectReference[] | undefined;
|
||||
const { fileNames, wildcardDirectories, spec } = getFileNames();
|
||||
const plugins = getPlugins();
|
||||
return {
|
||||
options,
|
||||
fileNames,
|
||||
@@ -2072,7 +2074,8 @@ namespace ts {
|
||||
errors,
|
||||
wildcardDirectories,
|
||||
compileOnSave: !!raw.compileOnSave,
|
||||
configFileSpecs: spec
|
||||
configFileSpecs: spec,
|
||||
plugins
|
||||
};
|
||||
|
||||
function getFileNames(): ExpandResult {
|
||||
@@ -2164,6 +2167,36 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getPlugins() {
|
||||
let plugins: (string | [string, any?])[] | undefined;
|
||||
if (hasProperty(raw, "plugins") && !isNullOrUndefined(raw.plugins)) {
|
||||
if (isArray(raw.plugins)) {
|
||||
for (const plugin of raw.plugins) {
|
||||
if (isPlugin(plugin)) {
|
||||
if (!plugins) {
|
||||
plugins = [plugin];
|
||||
}
|
||||
else {
|
||||
plugins.push(plugin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO(rbuckton): report diagnostic
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO(rbuckton): report diagnostic
|
||||
}
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
function isPlugin(plugin: any): plugin is string | [string, any] {
|
||||
return typeof plugin === "string" ||
|
||||
isArray(plugin) && plugin.length >= 1 && plugin.length <= 2 && typeof plugin[0] === "string";
|
||||
}
|
||||
|
||||
function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) {
|
||||
if (!sourceFile) {
|
||||
errors.push(createCompilerDiagnostic(message, arg0, arg1));
|
||||
|
||||
@@ -3180,6 +3180,22 @@
|
||||
"category": "Error",
|
||||
"code": 5075
|
||||
},
|
||||
"'package.json' field '{0}' requires a value of type {1}.": {
|
||||
"category": "Error",
|
||||
"code": 5076
|
||||
},
|
||||
"Plugin '{0}' could not be found: {1}.": {
|
||||
"category": "Error",
|
||||
"code": 5077
|
||||
},
|
||||
"Plugin dependencies for '{0}' could not resolved.": {
|
||||
"category": "Error",
|
||||
"code": 5078
|
||||
},
|
||||
"Plugin '{0}' failed while executing the '{1}' hook: {2}": {
|
||||
"category": "Error",
|
||||
"code": 5079
|
||||
},
|
||||
|
||||
"Generates a sourcemap for each corresponding '.d.ts' file.": {
|
||||
"category": "Message",
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
namespace ts {
|
||||
type Hook = "activate" | "deactivate" | "preEmit";
|
||||
type HookFunction<K extends Hook> = NonNullable<CompilerPluginModule[K]>;
|
||||
|
||||
type ExecuteUserCodeResult<T> =
|
||||
| { error: Diagnostic, result: undefined }
|
||||
| { error: undefined, result: T | undefined };
|
||||
|
||||
interface PluginEntry {
|
||||
active: boolean; // Tracks whether the plugin has been activated.
|
||||
compilerPlugin: CompilerPlugin;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface GetPluginsResult {
|
||||
plugins: CompilerPlugin[];
|
||||
diagnostics?: Diagnostic[];
|
||||
}
|
||||
|
||||
interface PackageJson {
|
||||
typescriptPlugin?: PluginPackageSettings;
|
||||
}
|
||||
|
||||
interface PluginPackageSettings {
|
||||
activationEvents?: string[];
|
||||
pluginDependencies?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the supplied plugins (and their dependencies) relative to an initial directory.
|
||||
*/
|
||||
/*@internal*/
|
||||
export function getPlugins(host: ModuleLoaderHost, initialDir: string, plugins: ReadonlyArray<string | [string, any?]>) {
|
||||
debugger;
|
||||
interface ResolvedModule {
|
||||
module: {};
|
||||
moduleName: string;
|
||||
modulePath: string | undefined;
|
||||
packageJsonPath?: string;
|
||||
}
|
||||
|
||||
const compilerPlugins: CompilerPlugin[] = [];
|
||||
const compilerPluginMap = createMap<CompilerPlugin>();
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
for (const plugin of plugins) {
|
||||
processPlugin(initialDir, plugin);
|
||||
}
|
||||
|
||||
return { plugins: compilerPlugins, diagnostics };
|
||||
|
||||
function processPlugin(initialDir: string, plugin: string | [string, any?]) {
|
||||
const originalName = isArray(plugin) ? plugin[0] : <string>plugin;
|
||||
const options = isArray(plugin) && plugin.length > 0 ? plugin[1] : undefined;
|
||||
|
||||
let candidates: string[];
|
||||
if (isExternalModuleNameRelative(originalName) || isTypeScriptPlugin(originalName)) {
|
||||
candidates = [originalName];
|
||||
}
|
||||
else {
|
||||
candidates = [addTypeScriptPluginPrefix(originalName), originalName];
|
||||
}
|
||||
|
||||
let compilerPlugin: CompilerPlugin | undefined;
|
||||
for (const candidate of candidates) {
|
||||
compilerPlugin = compilerPluginMap.get(candidate);
|
||||
if (compilerPlugin) {
|
||||
if (!isNullOrUndefined(options) && compilerPlugin.options === undefined) {
|
||||
compilerPlugin.options = options;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedModule: ResolvedModule | undefined;
|
||||
let firstError: { stack?: string, message?: string } | undefined;
|
||||
for (const candidate of candidates) {
|
||||
const result = host.require(initialDir, candidate);
|
||||
if (result.module) {
|
||||
resolvedModule = {
|
||||
module: result.module,
|
||||
moduleName: candidate,
|
||||
modulePath: result.modulePath,
|
||||
packageJsonPath: !isExternalModuleNameRelative(candidate) && result.modulePath
|
||||
? findPackageJsonPath(host, result.modulePath)
|
||||
: undefined
|
||||
};
|
||||
break;
|
||||
}
|
||||
if (!firstError) firstError = result.error;
|
||||
}
|
||||
|
||||
if (!resolvedModule) {
|
||||
const error = Debug.assertDefined(firstError);
|
||||
reportError(Diagnostics.Plugin_0_could_not_be_found_Colon_1, originalName, error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJson = resolvedModule.packageJsonPath
|
||||
? readJson(resolvedModule.packageJsonPath, host) as PackageJson
|
||||
: undefined;
|
||||
|
||||
let activationEvents: string[] | undefined;
|
||||
let pluginDependencies: string[] | undefined;
|
||||
if (!isNullOrUndefined(packageJson) && typeof packageJson === "object") {
|
||||
if (hasProperty(packageJson, "typescriptPlugin") && !isNullOrUndefined(packageJson.typescriptPlugin)) {
|
||||
const typescriptPlugin = packageJson.typescriptPlugin;
|
||||
if (typeof typescriptPlugin === "object") {
|
||||
if (hasProperty(typescriptPlugin, "activationEvents") && !isNullOrUndefined(typescriptPlugin.activationEvents)) {
|
||||
if (isArray(typescriptPlugin.activationEvents)) {
|
||||
for (const event of typescriptPlugin.activationEvents) {
|
||||
if (typeof event !== "string") {
|
||||
reportError(Diagnostics.package_json_field_0_requires_a_value_of_type_1, "typescriptPlugin.activationEvents[]", "string");
|
||||
}
|
||||
else {
|
||||
activationEvents = append(activationEvents, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportError(Diagnostics.package_json_field_0_requires_a_value_of_type_1, "typescriptPlugin.activationEvents", "Array");
|
||||
}
|
||||
}
|
||||
if (hasProperty(typescriptPlugin, "pluginDependencies") && !isNullOrUndefined(typescriptPlugin.pluginDependencies)) {
|
||||
if (isArray(typescriptPlugin.pluginDependencies)) {
|
||||
for (const dependency of typescriptPlugin.pluginDependencies) {
|
||||
if (typeof dependency !== "string") {
|
||||
reportError(Diagnostics.package_json_field_0_requires_a_value_of_type_1, "typescriptPlugin.pluginDependencies[]", "string");
|
||||
}
|
||||
else {
|
||||
pluginDependencies = append(pluginDependencies, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportError(Diagnostics.package_json_field_0_requires_a_value_of_type_1, "typescriptPlugin.pluginDependencies", "Array");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportError(Diagnostics.package_json_field_0_requires_a_value_of_type_1, "typescriptPlugin", "object");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compilerPlugin = {
|
||||
originalName,
|
||||
name: resolvedModule.moduleName,
|
||||
path: resolvedModule.modulePath,
|
||||
plugin: resolvedModule.module,
|
||||
pluginDependencies,
|
||||
activationEvents,
|
||||
options
|
||||
};
|
||||
|
||||
compilerPluginMap.set(compilerPlugin.name, compilerPlugin);
|
||||
if (compilerPlugin.pluginDependencies) {
|
||||
if (compilerPlugin.path) {
|
||||
const initialDir = getDirectoryPath(compilerPlugin.path);
|
||||
for (const plugin of compilerPlugin.pluginDependencies) {
|
||||
processPlugin(initialDir, plugin);
|
||||
}
|
||||
}
|
||||
else {
|
||||
reportError(Diagnostics.Plugin_dependencies_for_0_could_not_resolved, compilerPlugin.name);
|
||||
}
|
||||
}
|
||||
compilerPlugins.push(compilerPlugin);
|
||||
}
|
||||
|
||||
function reportError(message: DiagnosticMessage, arg0?: string, arg1?: string) {
|
||||
diagnostics.push(createCompilerDiagnostic(message, arg0, arg1));
|
||||
}
|
||||
}
|
||||
|
||||
function findPackageJsonPath(host: ModuleResolutionHost, modulePath: string) {
|
||||
while (true) {
|
||||
const candidate = combinePaths(modulePath, "package.json");
|
||||
if (host.fileExists(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
const parentPath = getDirectoryPath(modulePath);
|
||||
if (modulePath === parentPath || !parentPath) {
|
||||
return undefined;
|
||||
}
|
||||
modulePath = parentPath;
|
||||
}
|
||||
}
|
||||
|
||||
function isTypeScriptPlugin(moduleName: string) {
|
||||
return /^(?:@[^\\/]+[\\/])?typescript-plugin-\w/.test(moduleName);
|
||||
}
|
||||
|
||||
function addTypeScriptPluginPrefix(moduleName: string) {
|
||||
// a module name like 'foo', 'foo/bar', or '@foo/bar`
|
||||
return moduleName.replace(/^(@[^\\/]+[\\/])?/, "$1typescript-plugin-");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `CompilerPluginHost` used to manage plugin lifetime.
|
||||
*/
|
||||
/*@internal*/
|
||||
export function createPluginHost(plugins: ReadonlyArray<CompilerPlugin>): CompilerPluginHost {
|
||||
const entries: PluginEntry[] = plugins.map(compilerPlugin => ({ compilerPlugin, active: false }));
|
||||
return {
|
||||
preEmit,
|
||||
deactivate
|
||||
};
|
||||
|
||||
function executeUserCode<K extends Hook>(compilerPlugin: CompilerPlugin, hook: K, ...args: Parameters<HookFunction<K>>): ExecuteUserCodeResult<ReturnType<HookFunction<K>>> {
|
||||
const hookFunction: HookFunction<K> | undefined = compilerPlugin.plugin[hook]!;
|
||||
if (typeof hookFunction === "function") {
|
||||
try {
|
||||
const result = hookFunction.apply(compilerPlugin.plugin, args);
|
||||
return { result, error: undefined };
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof OperationCanceledException) {
|
||||
throw error;
|
||||
}
|
||||
return { result: undefined, error: createUserCodeDiagnostic(compilerPlugin, hook, error) };
|
||||
}
|
||||
}
|
||||
return { result: undefined, error: undefined };
|
||||
}
|
||||
|
||||
function createContext(compilerHost: CompilerHost, { options }: CompilerPlugin): CompilerPluginContext {
|
||||
return { ts, compilerHost, options };
|
||||
}
|
||||
|
||||
function createUserCodeDiagnostic(compilerPlugin: CompilerPlugin, hook: Hook, error: { message?: string, stack?: string }) {
|
||||
return createCompilerDiagnostic(Diagnostics.Plugin_0_failed_while_executing_the_1_hook_Colon_2, compilerPlugin.name, hook, error.stack || error.message || error.toString());
|
||||
}
|
||||
|
||||
function activate(host: CompilerHost, activationEventName: string) {
|
||||
const activeCompilerPlugins: CompilerPlugin[] = [];
|
||||
let diagnostics: ReadonlyArray<Diagnostic> | undefined;
|
||||
for (const entry of entries) {
|
||||
if (!entry.active && contains(entry.compilerPlugin.activationEvents, activationEventName)) {
|
||||
const { error, result: activationResult } = executeUserCode(entry.compilerPlugin, "activate", createContext(host, entry.compilerPlugin));
|
||||
if (error) {
|
||||
diagnostics = concatenate(diagnostics, [error]);
|
||||
}
|
||||
else {
|
||||
if (activationResult) diagnostics = concatenate(diagnostics, activationResult.diagnostics);
|
||||
entry.active = true;
|
||||
}
|
||||
}
|
||||
if (entry.active) {
|
||||
activeCompilerPlugins.push(entry.compilerPlugin);
|
||||
}
|
||||
}
|
||||
return { activeCompilerPlugins, diagnostics };
|
||||
}
|
||||
|
||||
function preEmit(host: CompilerHost, program: Program, targetSourceFile?: SourceFile, cancellationToken?: CancellationToken): CompilerPluginPreEmitResult {
|
||||
debugger;
|
||||
const activationResult = activate(host, /*activationEventName*/ "preEmit");
|
||||
const activeCompilerPlugins = activationResult.activeCompilerPlugins;
|
||||
let diagnostics = activationResult.diagnostics;
|
||||
let customTransformers: CustomTransformers | undefined;
|
||||
for (const plugin of activeCompilerPlugins) {
|
||||
const { error, result: preEmitResult } = executeUserCode(plugin, "preEmit", createContext(host, plugin), program, targetSourceFile, cancellationToken);
|
||||
if (error) {
|
||||
diagnostics = concatenate(diagnostics, [error]);
|
||||
}
|
||||
else if (preEmitResult) {
|
||||
diagnostics = concatenate(diagnostics, preEmitResult.diagnostics);
|
||||
customTransformers = combineCustomTransformers(customTransformers, preEmitResult.customTransformers);
|
||||
}
|
||||
}
|
||||
return { diagnostics, customTransformers };
|
||||
}
|
||||
|
||||
function deactivate(host: CompilerHost): CompilerPluginDeactivationResult {
|
||||
debugger;
|
||||
let diagnostics: Diagnostic[] | undefined;
|
||||
for (const entry of entries) {
|
||||
if (entry.active) {
|
||||
entry.active = false;
|
||||
const { error } = executeUserCode(entry.compilerPlugin, "deactivate", createContext(host, entry.compilerPlugin));
|
||||
if (error) {
|
||||
diagnostics = append(diagnostics, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { diagnostics };
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-6
@@ -725,6 +725,7 @@ namespace ts {
|
||||
const ambientModuleNameToUnmodifiedFileName = createMap<string>();
|
||||
// Todo:: Use this to report why file was included in --extendedDiagnostics
|
||||
let refFileMap: MultiMap<ts.RefFile> | undefined;
|
||||
const pluginHost = createProgramOptions.plugins && createPluginHost(createProgramOptions.plugins);
|
||||
|
||||
const cachedSemanticDiagnosticsForFile: DiagnosticCache<Diagnostic> = {};
|
||||
const cachedDeclarationDiagnosticsForFile: DiagnosticCache<DiagnosticWithLocation> = {};
|
||||
@@ -910,6 +911,8 @@ namespace ts {
|
||||
host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false);
|
||||
}
|
||||
});
|
||||
|
||||
oldProgram.dispose();
|
||||
}
|
||||
|
||||
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
|
||||
@@ -958,7 +961,8 @@ namespace ts {
|
||||
getResolvedProjectReferenceToRedirect,
|
||||
getResolvedProjectReferenceByPath,
|
||||
forEachResolvedProjectReference,
|
||||
emitBuildInfo
|
||||
emitBuildInfo,
|
||||
dispose
|
||||
};
|
||||
|
||||
verifyCompilerOptions();
|
||||
@@ -967,6 +971,12 @@ namespace ts {
|
||||
|
||||
return program;
|
||||
|
||||
function dispose() {
|
||||
if (pluginHost) {
|
||||
pluginHost.deactivate(host);
|
||||
}
|
||||
}
|
||||
|
||||
function compareDefaultLibFiles(a: SourceFile, b: SourceFile) {
|
||||
return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b));
|
||||
}
|
||||
@@ -1544,11 +1554,18 @@ namespace ts {
|
||||
function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
|
||||
let declarationDiagnostics: ReadonlyArray<Diagnostic> = [];
|
||||
|
||||
if (!emitOnlyDtsFiles) {
|
||||
if (options.noEmit) {
|
||||
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
|
||||
}
|
||||
if (!emitOnlyDtsFiles && options.noEmit) {
|
||||
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
|
||||
}
|
||||
|
||||
let pluginDiagnostics: ReadonlyArray<Diagnostic> | undefined;
|
||||
if (pluginHost) {
|
||||
const result = pluginHost.preEmit(host, program, sourceFile, cancellationToken);
|
||||
pluginDiagnostics = result.diagnostics;
|
||||
customTransformers = combineCustomTransformers(customTransformers, result.customTransformers);
|
||||
}
|
||||
|
||||
if (!emitOnlyDtsFiles) {
|
||||
// If the noEmitOnError flag is set, then check if we have any errors so far. If so,
|
||||
// immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we
|
||||
// get any preEmit diagnostics, not just the ones
|
||||
@@ -1557,7 +1574,8 @@ namespace ts {
|
||||
...program.getOptionsDiagnostics(cancellationToken),
|
||||
...program.getSyntacticDiagnostics(sourceFile, cancellationToken),
|
||||
...program.getGlobalDiagnostics(cancellationToken),
|
||||
...program.getSemanticDiagnostics(sourceFile, cancellationToken)
|
||||
...program.getSemanticDiagnostics(sourceFile, cancellationToken),
|
||||
...(pluginDiagnostics || [])
|
||||
];
|
||||
|
||||
if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) {
|
||||
|
||||
@@ -651,6 +651,7 @@ namespace ts {
|
||||
base64decode?(input: string): string;
|
||||
base64encode?(input: string): string;
|
||||
/*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer;
|
||||
/*@internal*/ require?(baseDir: string, moduleName: string): RequireResult;
|
||||
}
|
||||
|
||||
export interface FileWatcher {
|
||||
@@ -838,6 +839,15 @@ namespace ts {
|
||||
bufferFrom,
|
||||
base64decode: input => bufferFrom(input, "base64").toString("utf8"),
|
||||
base64encode: input => bufferFrom(input).toString("base64"),
|
||||
require: (baseDir, moduleName) => {
|
||||
try {
|
||||
const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem);
|
||||
return { module: require(modulePath), modulePath, error: undefined };
|
||||
}
|
||||
catch (error) {
|
||||
return { module: undefined, modulePath: undefined, error };
|
||||
}
|
||||
}
|
||||
};
|
||||
return nodeSystem;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"transformer.ts",
|
||||
"emitter.ts",
|
||||
"watchUtilities.ts",
|
||||
"plugin.ts",
|
||||
"program.ts",
|
||||
"builderState.ts",
|
||||
"builder.ts",
|
||||
|
||||
@@ -2947,6 +2947,27 @@ namespace ts {
|
||||
file: Path;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface CompilerPluginDeactivationResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The CompilerPluginHost provides an interface for interacting with the compiler plugin model.
|
||||
*/
|
||||
/*@internal*/
|
||||
export interface CompilerPluginHost {
|
||||
/**
|
||||
* Trigger the `preEmit` hooks for plugins.
|
||||
*/
|
||||
preEmit(host: CompilerHost, program: Program, targetSourceFile?: SourceFile, cancellationToken?: CancellationToken): CompilerPluginPreEmitResult;
|
||||
|
||||
/**
|
||||
* Deactivate any active plugins.
|
||||
*/
|
||||
deactivate(host: CompilerHost): CompilerPluginDeactivationResult;
|
||||
}
|
||||
|
||||
// TODO: This should implement TypeCheckerHost but that's an internal type.
|
||||
export interface Program extends ScriptReferenceHost {
|
||||
|
||||
@@ -3038,6 +3059,10 @@ namespace ts {
|
||||
/*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
|
||||
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
|
||||
/*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
|
||||
/**
|
||||
* Dispose of any resources held by the program and deactivate any active plugins.
|
||||
*/
|
||||
/*@internal*/ dispose(): void;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -4871,6 +4896,7 @@ namespace ts {
|
||||
errors: Diagnostic[];
|
||||
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
|
||||
compileOnSave?: boolean;
|
||||
plugins?: (string | [string, any?])[];
|
||||
/* @internal */ configFileSpecs?: ConfigFileSpecs;
|
||||
}
|
||||
|
||||
@@ -4901,11 +4927,98 @@ namespace ts {
|
||||
/* @internal */ spec: ConfigFileSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* A context object passed to a plugin during activation.
|
||||
*/
|
||||
export interface CompilerPluginContext {
|
||||
/**
|
||||
* The running instance of the TypeScript compiler.
|
||||
*/
|
||||
readonly ts: typeof ts;
|
||||
|
||||
/**
|
||||
* The current CompilerHost.
|
||||
*/
|
||||
readonly compilerHost: CompilerHost;
|
||||
|
||||
/**
|
||||
* Configuration options for the plugin.
|
||||
*/
|
||||
readonly options: MapLike<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional result that can be returned from the `CompilerPluginModule.activate` hook.
|
||||
*/
|
||||
export interface CompilerPluginActivationResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional result that can be returned from the `CompilerPluginModule.preEmit` hook.
|
||||
*/
|
||||
export interface CompilerPluginPreEmitResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
customTransformers?: CustomTransformers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the supported shape of the main module for a compiler plugin.
|
||||
*/
|
||||
export interface CompilerPluginModule {
|
||||
/**
|
||||
* The `activate` hook is invoked when a plugin is activated for the first time within a `Program`.
|
||||
* @param context The current plugin context.
|
||||
*/
|
||||
activate?(context: CompilerPluginContext): CompilerPluginActivationResult | void;
|
||||
/**
|
||||
* The `preEmit` hook is invoked after type check has completed and immediately before emit.
|
||||
* @param context The current plugin context.
|
||||
* @param program The current `Program`.
|
||||
* @param targetSourceFile The `SourceFile` that is about to be emitted, or `undefined` when emitting all outputs.
|
||||
* @param cancellationToken A `CancellationToken` that can be used to abort an operation when running in the language service.
|
||||
*/
|
||||
preEmit?(context: CompilerPluginContext, program: Program, targetSourceFile?: SourceFile, cancellationToken?: CancellationToken): CompilerPluginPreEmitResult | void;
|
||||
/**
|
||||
* The `deactivate` hook is invoked when a plugin should be deactivated so that it can free up any shared resources.
|
||||
* @param context The current plugin context.
|
||||
*/
|
||||
deactivate?(context: CompilerPluginContext): void;
|
||||
}
|
||||
|
||||
export interface CompilerPlugin {
|
||||
/** The rsolved package name for the plugin. */
|
||||
name: string;
|
||||
/** The path to the plugin. */
|
||||
path: string | undefined;
|
||||
/** The originally-specified package name for the plugin */
|
||||
originalName?: string;
|
||||
/** Any configuration options provided to the plugin. */
|
||||
options?: any;
|
||||
/** The hooks supported by the plugin. */
|
||||
activationEvents?: string[];
|
||||
/** Plugin dependencies that should be loaded before this plugin. */
|
||||
pluginDependencies?: string[];
|
||||
/** The resolved module object for the plugin. */
|
||||
plugin: CompilerPluginModule;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface ModuleLoaderHost extends ModuleResolutionHost {
|
||||
require(initialDir: string, moduleName: string): RequireResult;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type RequireResult =
|
||||
| { module: {}, modulePath?: string, error: undefined }
|
||||
| { module: undefined, modulePath?: undefined, error: { stack?: string, message?: string } };
|
||||
|
||||
export interface CreateProgramOptions {
|
||||
rootNames: ReadonlyArray<string>;
|
||||
options: CompilerOptions;
|
||||
projectReferences?: ReadonlyArray<ProjectReference>;
|
||||
host?: CompilerHost;
|
||||
plugins?: ReadonlyArray<CompilerPlugin>;
|
||||
oldProgram?: Program;
|
||||
configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
|
||||
@@ -4712,6 +4712,16 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function combineCustomTransformers(left: CustomTransformers | undefined, right: CustomTransformers | undefined): CustomTransformers | undefined {
|
||||
if (!left) return right;
|
||||
if (!right) return left;
|
||||
return {
|
||||
before: concatenate(left.before, right.before),
|
||||
after: concatenate(left.after, right.after),
|
||||
afterDeclarations: concatenate(left.afterDeclarations, right.afterDeclarations)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
|
||||
+26
-3
@@ -238,20 +238,43 @@ namespace compiler {
|
||||
return new CompilationResult(host, compilerOptions, /*program*/ undefined, /*result*/ undefined, project.errors);
|
||||
}
|
||||
if (project.config) {
|
||||
rootFiles = project.config.fileNames;
|
||||
compilerOptions = project.config.options;
|
||||
return compileProject(host, project.config, project.file);
|
||||
}
|
||||
}
|
||||
delete compilerOptions.project;
|
||||
}
|
||||
|
||||
return compileProject(host, { options: compilerOptions, fileNames: rootFiles || [], errors: [], });
|
||||
}
|
||||
|
||||
export function compileProject(host: fakes.CompilerHost, project: ts.ParsedCommandLine, configFileName?: string) {
|
||||
const compilerOptions = { ...project.options };
|
||||
|
||||
let plugins: ts.CreateProgramOptions["plugins"];
|
||||
if (project.plugins) {
|
||||
if (!configFileName) throw new Error("Cannot load plugins without a config file.");
|
||||
if (!host.sys.require) throw new Error("Plugins cannot be loaded if provided System does not support `require()`.");
|
||||
|
||||
const result = ts.getPlugins(host.sys as ts.ModuleLoaderHost, ts.getDirectoryPath(configFileName), project.plugins);
|
||||
if (ts.some(result.diagnostics)) {
|
||||
return new CompilationResult(host, compilerOptions, /*program*/ undefined, /*result*/ undefined, result.diagnostics);
|
||||
}
|
||||
|
||||
plugins = result.plugins;
|
||||
}
|
||||
|
||||
// establish defaults (aligns with old harness)
|
||||
if (compilerOptions.target === undefined) compilerOptions.target = ts.ScriptTarget.ES3;
|
||||
if (compilerOptions.newLine === undefined) compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed;
|
||||
if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true;
|
||||
if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true;
|
||||
|
||||
const program = ts.createProgram(rootFiles || [], compilerOptions, host);
|
||||
const program = ts.createProgram({
|
||||
rootNames: project.fileNames || [],
|
||||
options: compilerOptions,
|
||||
host,
|
||||
plugins
|
||||
});
|
||||
const emitResult = program.emit();
|
||||
const errors = ts.getPreEmitDiagnostics(program);
|
||||
return new CompilationResult(host, compilerOptions, program, emitResult, errors);
|
||||
|
||||
+64
-1
@@ -8,6 +8,7 @@ namespace fakes {
|
||||
executingFilePath?: string;
|
||||
newLine?: "\r\n" | "\n";
|
||||
env?: Record<string, string>;
|
||||
useFakeLoader?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,16 +21,29 @@ namespace fakes {
|
||||
public readonly newLine: string;
|
||||
public readonly useCaseSensitiveFileNames: boolean;
|
||||
public exitCode: number | undefined;
|
||||
public require?: ts.System["require"];
|
||||
|
||||
private readonly _executingFilePath: string | undefined;
|
||||
private readonly _env: Record<string, string> | undefined;
|
||||
|
||||
constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\r\n", env }: SystemOptions = {}) {
|
||||
constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\r\n", env, useFakeLoader }: SystemOptions = {}) {
|
||||
this.vfs = vfs.isReadonly ? vfs.shadow() : vfs;
|
||||
this.useCaseSensitiveFileNames = !this.vfs.ignoreCase;
|
||||
this.newLine = newLine;
|
||||
this._executingFilePath = executingFilePath;
|
||||
this._env = env;
|
||||
if (useFakeLoader) {
|
||||
const loader = new ModuleLoader(this.vfs);
|
||||
this.require = (baseDir: string, moduleName: string): ts.RequireResult => {
|
||||
try {
|
||||
const modulePath = ts.resolveJSModule(moduleName, baseDir, this);
|
||||
return { module: loader.require(modulePath), modulePath, error: undefined };
|
||||
}
|
||||
catch (error) {
|
||||
return { module: undefined, modulePath: undefined, error };
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public write(message: string) {
|
||||
@@ -375,6 +389,55 @@ namespace fakes {
|
||||
}
|
||||
}
|
||||
|
||||
type ModuleWrapper = (require: Function, module: any, exports: any, dirname: string, filename: string) => void;
|
||||
|
||||
interface ModuleObject {
|
||||
exports: any;
|
||||
}
|
||||
|
||||
class ModuleLoader {
|
||||
readonly vfs: vfs.FileSystem;
|
||||
private _modules = ts.createMap<ModuleObject>();
|
||||
|
||||
constructor(vfs: vfs.FileSystem) {
|
||||
this.vfs = vfs;
|
||||
}
|
||||
|
||||
require(modulePath: string) {
|
||||
// this is a very basic loader that is only intended to load a single file for now.
|
||||
let module = this._modules.get(modulePath);
|
||||
if (!module) {
|
||||
module = { exports: {} };
|
||||
this._modules.set(modulePath, module);
|
||||
const moduleContent = this.vfs.readFileSync(modulePath, "utf8");
|
||||
const linkedModule = this.link(modulePath, moduleContent);
|
||||
linkedModule(ts.notImplemented, module, module.exports, ts.getDirectoryPath(modulePath), modulePath);
|
||||
}
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
private link(modulePath: string, moduleContent: string) {
|
||||
let vm: typeof import("vm") | undefined;
|
||||
try {
|
||||
vm = require("vm");
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
return vm ? this.linkUsingNodeVM(vm, modulePath, moduleContent) :
|
||||
this.linkUsingFunction(modulePath, moduleContent);
|
||||
}
|
||||
|
||||
private linkUsingNodeVM(vm: typeof import("vm"), modulePath: string, moduleContent: string): ModuleWrapper {
|
||||
return vm.runInThisContext(`(function (require, module, exports, __dirname, __filename) {${moduleContent}\n})`, { filename: modulePath });
|
||||
}
|
||||
|
||||
private linkUsingFunction(modulePath: string, moduleContent: string): ModuleWrapper {
|
||||
const indirectEval = eval;
|
||||
return indirectEval(`(function (require, module, exports, __dirname, __filename) {${moduleContent}\n//# sourceURL=${modulePath}\n})`);
|
||||
}
|
||||
}
|
||||
|
||||
export type ExpectedDiagnosticMessage = [ts.DiagnosticMessage, ...(string | number)[]];
|
||||
export interface ExpectedDiagnosticMessageChain {
|
||||
message: ExpectedDiagnosticMessage;
|
||||
|
||||
@@ -816,7 +816,9 @@ namespace Harness {
|
||||
compilerOptions: ts.CompilerOptions | undefined,
|
||||
// Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file
|
||||
currentDirectory: string | undefined,
|
||||
symlinks?: vfs.FileSet
|
||||
symlinks?: vfs.FileSet,
|
||||
plugins?: ts.ParsedCommandLine["plugins"],
|
||||
configFileName?: string
|
||||
): compiler.CompilationResult {
|
||||
const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.cloneCompilerOptions(compilerOptions) : { noResolve: false };
|
||||
options.target = options.target || ts.ScriptTarget.ES3;
|
||||
@@ -857,8 +859,12 @@ namespace Harness {
|
||||
if (symlinks) {
|
||||
fs.apply(symlinks);
|
||||
}
|
||||
const host = new fakes.CompilerHost(fs, options);
|
||||
const result = compiler.compileFiles(host, programFileNames, options);
|
||||
|
||||
const sys = new fakes.System(fs, { useFakeLoader: !!(plugins && configFileName) });
|
||||
const host = new fakes.CompilerHost(sys, options);
|
||||
const result = plugins && configFileName
|
||||
? compiler.compileProject(host, { options, fileNames: programFileNames, errors: [], plugins }, configFileName)
|
||||
: compiler.compileFiles(host, programFileNames, options);
|
||||
result.symlinks = symlinks;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -789,7 +789,7 @@ namespace Harness.LanguageService {
|
||||
return mockHash(s);
|
||||
}
|
||||
|
||||
require(_initialDir: string, _moduleName: string): ts.server.RequireResult {
|
||||
require(_initialDir: string, _moduleName: string): ts.RequireResult {
|
||||
switch (_moduleName) {
|
||||
// Adds to the Quick Info a fixed string and a string from the config file
|
||||
// and replaces the first display part
|
||||
|
||||
@@ -346,7 +346,7 @@ interface Array<T> { length: number; [n: number]: T; }`
|
||||
private readonly currentDirectory: string;
|
||||
private readonly customWatchFile: HostWatchFile | undefined;
|
||||
private readonly customRecursiveWatchDirectory: HostWatchDirectory | undefined;
|
||||
public require: ((initialPath: string, moduleName: string) => server.RequireResult) | undefined;
|
||||
public require: ((initialPath: string, moduleName: string) => RequireResult) | undefined;
|
||||
|
||||
constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, executingFilePath: string, currentDirectory: string, fileOrFolderorSymLinkList: ReadonlyArray<FileOrFolderOrSymLink>, public readonly newLine = "\n", public readonly useWindowsStylePath?: boolean, private readonly environmentVariables?: Map<string>) {
|
||||
this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
|
||||
@@ -5,7 +5,6 @@ declare namespace ts.server {
|
||||
data: any;
|
||||
}
|
||||
|
||||
type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } };
|
||||
export interface ServerHost extends System {
|
||||
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
|
||||
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
|
||||
@@ -15,6 +14,5 @@ declare namespace ts.server {
|
||||
clearImmediate(timeoutId: any): void;
|
||||
gc?(): void;
|
||||
trace?(s: string): void;
|
||||
require?(initialPath: string, moduleName: string): RequireResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,13 +162,17 @@ class CompilerTest {
|
||||
const units = testCaseContent.testUnitData;
|
||||
this.harnessSettings = testCaseContent.settings;
|
||||
let tsConfigOptions: ts.CompilerOptions | undefined;
|
||||
let plugins: ts.ParsedCommandLine["plugins"];
|
||||
let tsConfigFileName: string | undefined;
|
||||
this.tsConfigFiles = [];
|
||||
if (testCaseContent.tsConfig) {
|
||||
assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
|
||||
assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`);
|
||||
|
||||
tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options);
|
||||
this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath!)));
|
||||
plugins = testCaseContent.tsConfig.plugins;
|
||||
tsConfigFileName = ts.combinePaths(rootDir, tsConfigOptions.configFilePath!);
|
||||
this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, tsConfigFileName));
|
||||
}
|
||||
else {
|
||||
const baseUrl = this.harnessSettings.baseUrl;
|
||||
@@ -210,7 +214,9 @@ class CompilerTest {
|
||||
this.harnessSettings,
|
||||
/*options*/ tsConfigOptions,
|
||||
/*currentDirectory*/ this.harnessSettings.currentDirectory,
|
||||
testCaseContent.symlinks
|
||||
testCaseContent.symlinks,
|
||||
plugins,
|
||||
tsConfigFileName
|
||||
);
|
||||
|
||||
this.options = this.result.options;
|
||||
|
||||
+20
-4
@@ -142,16 +142,30 @@ namespace ts {
|
||||
sys.write(JSON.stringify(convertToTSConfig(configParseResult, configFileName, sys), null, 4) + sys.newLine);
|
||||
return sys.exit(ExitStatus.Success);
|
||||
}
|
||||
let plugins: ReadonlyArray<CompilerPlugin> | undefined;
|
||||
if (configParseResult.plugins) {
|
||||
if (sys.require) {
|
||||
const result = getPlugins(sys as ModuleLoaderHost, getDirectoryPath(configFileName), configParseResult.plugins);
|
||||
plugins = result.plugins;
|
||||
if (some(result.diagnostics)) {
|
||||
// TODO(rbuckton): report diagnostics
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO(rbuckton): report diagnostic
|
||||
}
|
||||
}
|
||||
updateReportDiagnostic(configParseResult.options);
|
||||
if (isWatchSet(configParseResult.options)) {
|
||||
reportWatchModeWithoutSysSupport();
|
||||
// TODO(rbuckton): watch mode support
|
||||
createWatchOfConfigFile(configParseResult, commandLineOptions);
|
||||
}
|
||||
else if (isIncrementalCompilation(configParseResult.options)) {
|
||||
performIncrementalCompilation(configParseResult);
|
||||
}
|
||||
else {
|
||||
performCompilation(configParseResult.fileNames, configParseResult.projectReferences, configParseResult.options, getConfigFileParsingDiagnostics(configParseResult));
|
||||
performCompilation(configParseResult.fileNames, configParseResult.projectReferences, configParseResult.options, plugins, getConfigFileParsingDiagnostics(configParseResult));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -169,7 +183,7 @@ namespace ts {
|
||||
performIncrementalCompilation(commandLine);
|
||||
}
|
||||
else {
|
||||
performCompilation(commandLine.fileNames, /*references*/ undefined, commandLineOptions);
|
||||
performCompilation(commandLine.fileNames, /*references*/ undefined, commandLineOptions, /*plugins*/ undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,7 +249,7 @@ namespace ts {
|
||||
undefined;
|
||||
}
|
||||
|
||||
function performCompilation(rootNames: string[], projectReferences: ReadonlyArray<ProjectReference> | undefined, options: CompilerOptions, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
|
||||
function performCompilation(rootNames: string[], projectReferences: ReadonlyArray<ProjectReference> | undefined, options: CompilerOptions, plugins: ReadonlyArray<CompilerPlugin> | undefined, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
|
||||
const host = createCompilerHost(options);
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
|
||||
@@ -247,7 +261,8 @@ namespace ts {
|
||||
options,
|
||||
projectReferences,
|
||||
host,
|
||||
configFileParsingDiagnostics
|
||||
configFileParsingDiagnostics,
|
||||
plugins
|
||||
};
|
||||
const program = createProgram(programOptions);
|
||||
const exitStatus = emitFilesAndReportErrorsAndGetExitStatus(
|
||||
@@ -257,6 +272,7 @@ namespace ts {
|
||||
createReportErrorSummary(options)
|
||||
);
|
||||
reportStatistics(program);
|
||||
program.dispose();
|
||||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
|
||||
@@ -903,10 +903,11 @@ namespace ts.server {
|
||||
|
||||
sys.require = (initialDir: string, moduleName: string): RequireResult => {
|
||||
try {
|
||||
return { module: require(resolveJSModule(moduleName, initialDir, sys)), error: undefined };
|
||||
const modulePath = resolveJSModule(moduleName, initialDir, sys);
|
||||
return { module: require(modulePath), modulePath, error: undefined };
|
||||
}
|
||||
catch (error) {
|
||||
return { module: undefined, error };
|
||||
return { module: undefined, modulePath: undefined, error };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+73
-11
@@ -2668,6 +2668,7 @@ declare namespace ts {
|
||||
errors: Diagnostic[];
|
||||
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
|
||||
compileOnSave?: boolean;
|
||||
plugins?: (string | [string, any?])[];
|
||||
}
|
||||
export enum WatchDirectoryFlags {
|
||||
None = 0,
|
||||
@@ -2677,11 +2678,81 @@ declare namespace ts {
|
||||
fileNames: string[];
|
||||
wildcardDirectories: MapLike<WatchDirectoryFlags>;
|
||||
}
|
||||
/**
|
||||
* A context object passed to a plugin during activation.
|
||||
*/
|
||||
interface CompilerPluginContext {
|
||||
/**
|
||||
* The running instance of the TypeScript compiler.
|
||||
*/
|
||||
readonly ts: typeof ts;
|
||||
/**
|
||||
* The current CompilerHost.
|
||||
*/
|
||||
readonly compilerHost: CompilerHost;
|
||||
/**
|
||||
* Configuration options for the plugin.
|
||||
*/
|
||||
readonly options: MapLike<any>;
|
||||
}
|
||||
/**
|
||||
* An optional result that can be returned from the `CompilerPluginModule.activate` hook.
|
||||
*/
|
||||
interface CompilerPluginActivationResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
/**
|
||||
* An optional result that can be returned from the `CompilerPluginModule.preEmit` hook.
|
||||
*/
|
||||
interface CompilerPluginPreEmitResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
customTransformers?: CustomTransformers;
|
||||
}
|
||||
/**
|
||||
* Describes the supported shape of the main module for a compiler plugin.
|
||||
*/
|
||||
interface CompilerPluginModule {
|
||||
/**
|
||||
* The `activate` hook is invoked when a plugin is activated for the first time within a `Program`.
|
||||
* @param context The current plugin context.
|
||||
*/
|
||||
activate?(context: CompilerPluginContext): CompilerPluginActivationResult | void;
|
||||
/**
|
||||
* The `preEmit` hook is invoked after type check has completed and immediately before emit.
|
||||
* @param context The current plugin context.
|
||||
* @param program The current `Program`.
|
||||
* @param targetSourceFile The `SourceFile` that is about to be emitted, or `undefined` when emitting all outputs.
|
||||
* @param cancellationToken A `CancellationToken` that can be used to abort an operation when running in the language service.
|
||||
*/
|
||||
preEmit?(context: CompilerPluginContext, program: Program, targetSourceFile?: SourceFile, cancellationToken?: CancellationToken): CompilerPluginPreEmitResult | void;
|
||||
/**
|
||||
* The `deactivate` hook is invoked when a plugin should be deactivated so that it can free up any shared resources.
|
||||
* @param context The current plugin context.
|
||||
*/
|
||||
deactivate?(context: CompilerPluginContext): void;
|
||||
}
|
||||
interface CompilerPlugin {
|
||||
/** The rsolved package name for the plugin. */
|
||||
name: string;
|
||||
/** The path to the plugin. */
|
||||
path: string | undefined;
|
||||
/** The originally-specified package name for the plugin */
|
||||
originalName?: string;
|
||||
/** Any configuration options provided to the plugin. */
|
||||
options?: any;
|
||||
/** The hooks supported by the plugin. */
|
||||
activationEvents?: string[];
|
||||
/** Plugin dependencies that should be loaded before this plugin. */
|
||||
pluginDependencies?: string[];
|
||||
/** The resolved module object for the plugin. */
|
||||
plugin: CompilerPluginModule;
|
||||
}
|
||||
export interface CreateProgramOptions {
|
||||
rootNames: ReadonlyArray<string>;
|
||||
options: CompilerOptions;
|
||||
projectReferences?: ReadonlyArray<ProjectReference>;
|
||||
host?: CompilerHost;
|
||||
plugins?: ReadonlyArray<CompilerPlugin>;
|
||||
oldProgram?: Program;
|
||||
configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
@@ -4280,6 +4351,8 @@ declare namespace ts {
|
||||
declare namespace ts {
|
||||
function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer;
|
||||
}
|
||||
declare namespace ts {
|
||||
}
|
||||
declare namespace ts {
|
||||
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined;
|
||||
export function resolveTripleslashReference(moduleName: string, containingFile: string): string;
|
||||
@@ -5803,16 +5876,6 @@ declare namespace ts.server {
|
||||
compressionKind: string;
|
||||
data: any;
|
||||
}
|
||||
type RequireResult = {
|
||||
module: {};
|
||||
error: undefined;
|
||||
} | {
|
||||
module: undefined;
|
||||
error: {
|
||||
stack?: string;
|
||||
message?: string;
|
||||
};
|
||||
};
|
||||
interface ServerHost extends System {
|
||||
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
|
||||
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
|
||||
@@ -5822,7 +5885,6 @@ declare namespace ts.server {
|
||||
clearImmediate(timeoutId: any): void;
|
||||
gc?(): void;
|
||||
trace?(s: string): void;
|
||||
require?(initialPath: string, moduleName: string): RequireResult;
|
||||
}
|
||||
}
|
||||
declare namespace ts.server {
|
||||
|
||||
+73
@@ -2668,6 +2668,7 @@ declare namespace ts {
|
||||
errors: Diagnostic[];
|
||||
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
|
||||
compileOnSave?: boolean;
|
||||
plugins?: (string | [string, any?])[];
|
||||
}
|
||||
export enum WatchDirectoryFlags {
|
||||
None = 0,
|
||||
@@ -2677,11 +2678,81 @@ declare namespace ts {
|
||||
fileNames: string[];
|
||||
wildcardDirectories: MapLike<WatchDirectoryFlags>;
|
||||
}
|
||||
/**
|
||||
* A context object passed to a plugin during activation.
|
||||
*/
|
||||
interface CompilerPluginContext {
|
||||
/**
|
||||
* The running instance of the TypeScript compiler.
|
||||
*/
|
||||
readonly ts: typeof ts;
|
||||
/**
|
||||
* The current CompilerHost.
|
||||
*/
|
||||
readonly compilerHost: CompilerHost;
|
||||
/**
|
||||
* Configuration options for the plugin.
|
||||
*/
|
||||
readonly options: MapLike<any>;
|
||||
}
|
||||
/**
|
||||
* An optional result that can be returned from the `CompilerPluginModule.activate` hook.
|
||||
*/
|
||||
interface CompilerPluginActivationResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
/**
|
||||
* An optional result that can be returned from the `CompilerPluginModule.preEmit` hook.
|
||||
*/
|
||||
interface CompilerPluginPreEmitResult {
|
||||
diagnostics?: ReadonlyArray<Diagnostic>;
|
||||
customTransformers?: CustomTransformers;
|
||||
}
|
||||
/**
|
||||
* Describes the supported shape of the main module for a compiler plugin.
|
||||
*/
|
||||
interface CompilerPluginModule {
|
||||
/**
|
||||
* The `activate` hook is invoked when a plugin is activated for the first time within a `Program`.
|
||||
* @param context The current plugin context.
|
||||
*/
|
||||
activate?(context: CompilerPluginContext): CompilerPluginActivationResult | void;
|
||||
/**
|
||||
* The `preEmit` hook is invoked after type check has completed and immediately before emit.
|
||||
* @param context The current plugin context.
|
||||
* @param program The current `Program`.
|
||||
* @param targetSourceFile The `SourceFile` that is about to be emitted, or `undefined` when emitting all outputs.
|
||||
* @param cancellationToken A `CancellationToken` that can be used to abort an operation when running in the language service.
|
||||
*/
|
||||
preEmit?(context: CompilerPluginContext, program: Program, targetSourceFile?: SourceFile, cancellationToken?: CancellationToken): CompilerPluginPreEmitResult | void;
|
||||
/**
|
||||
* The `deactivate` hook is invoked when a plugin should be deactivated so that it can free up any shared resources.
|
||||
* @param context The current plugin context.
|
||||
*/
|
||||
deactivate?(context: CompilerPluginContext): void;
|
||||
}
|
||||
interface CompilerPlugin {
|
||||
/** The rsolved package name for the plugin. */
|
||||
name: string;
|
||||
/** The path to the plugin. */
|
||||
path: string | undefined;
|
||||
/** The originally-specified package name for the plugin */
|
||||
originalName?: string;
|
||||
/** Any configuration options provided to the plugin. */
|
||||
options?: any;
|
||||
/** The hooks supported by the plugin. */
|
||||
activationEvents?: string[];
|
||||
/** Plugin dependencies that should be loaded before this plugin. */
|
||||
pluginDependencies?: string[];
|
||||
/** The resolved module object for the plugin. */
|
||||
plugin: CompilerPluginModule;
|
||||
}
|
||||
export interface CreateProgramOptions {
|
||||
rootNames: ReadonlyArray<string>;
|
||||
options: CompilerOptions;
|
||||
projectReferences?: ReadonlyArray<ProjectReference>;
|
||||
host?: CompilerHost;
|
||||
plugins?: ReadonlyArray<CompilerPlugin>;
|
||||
oldProgram?: Program;
|
||||
configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
@@ -4280,6 +4351,8 @@ declare namespace ts {
|
||||
declare namespace ts {
|
||||
function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer;
|
||||
}
|
||||
declare namespace ts {
|
||||
}
|
||||
declare namespace ts {
|
||||
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined;
|
||||
export function resolveTripleslashReference(moduleName: string, containingFile: string): string;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//// [tests/cases/conformance/plugins/transformerPlugin.ts] ////
|
||||
|
||||
//// [package.json]
|
||||
{
|
||||
"name": "typescript-plugin-transform",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"typescriptPlugin": {
|
||||
"activationEvents": ["preEmit"]
|
||||
}
|
||||
}
|
||||
|
||||
//// [index.js]
|
||||
exports.preEmit = function (context, program, targetSourceFile) {
|
||||
const ts = context.ts;
|
||||
return { customTransformers: { after: [replaceUndefinedWithVoid0] } };
|
||||
function replaceUndefinedWithVoid0(context) {
|
||||
const previousOnSubstituteNode = context.onSubstituteNode;
|
||||
context.enableSubstitution(ts.SyntaxKind.Identifier);
|
||||
context.onSubstituteNode = (hint, node) => {
|
||||
node = previousOnSubstituteNode(hint, node);
|
||||
if (hint === ts.EmitHint.Expression && ts.isIdentifier(node) && node.escapedText === "undefined") {
|
||||
node = ts.createPartiallyEmittedExpression(
|
||||
ts.addSyntheticTrailingComment(
|
||||
ts.setTextRange(
|
||||
ts.createVoidZero(),
|
||||
node),
|
||||
ts.SyntaxKind.MultiLineCommentTrivia, "undefined"));
|
||||
}
|
||||
return node;
|
||||
};
|
||||
return (file) => file;
|
||||
}
|
||||
};
|
||||
|
||||
//// [main.ts]
|
||||
const a = undefined;
|
||||
|
||||
//// [main.js]
|
||||
var a = void 0 /*undefined*/;
|
||||
@@ -0,0 +1,5 @@
|
||||
=== tests/cases/conformance/plugins/main.ts ===
|
||||
const a = undefined;
|
||||
>a : Symbol(a, Decl(main.ts, 0, 5))
|
||||
>undefined : Symbol(undefined)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
=== tests/cases/conformance/plugins/main.ts ===
|
||||
const a = undefined;
|
||||
>a : any
|
||||
>undefined : undefined
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// @noImplicitReferences: true
|
||||
// @filename: node_modules/typescript-plugin-transform/package.json
|
||||
{
|
||||
"name": "typescript-plugin-transform",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"typescriptPlugin": {
|
||||
"activationEvents": ["preEmit"]
|
||||
}
|
||||
}
|
||||
|
||||
// @filename: node_modules/typescript-plugin-transform/index.js
|
||||
exports.preEmit = function (context, program, targetSourceFile) {
|
||||
const ts = context.ts;
|
||||
return { customTransformers: { after: [replaceUndefinedWithVoid0] } };
|
||||
function replaceUndefinedWithVoid0(context) {
|
||||
const previousOnSubstituteNode = context.onSubstituteNode;
|
||||
context.enableSubstitution(ts.SyntaxKind.Identifier);
|
||||
context.onSubstituteNode = (hint, node) => {
|
||||
node = previousOnSubstituteNode(hint, node);
|
||||
if (hint === ts.EmitHint.Expression && ts.isIdentifier(node) && node.escapedText === "undefined") {
|
||||
node = ts.createPartiallyEmittedExpression(
|
||||
ts.addSyntheticTrailingComment(
|
||||
ts.setTextRange(
|
||||
ts.createVoidZero(),
|
||||
node),
|
||||
ts.SyntaxKind.MultiLineCommentTrivia, "undefined"));
|
||||
}
|
||||
return node;
|
||||
};
|
||||
return (file) => file;
|
||||
}
|
||||
};
|
||||
|
||||
// @filename: tsconfig.json
|
||||
{
|
||||
"compilerOptions": {},
|
||||
"plugins": [
|
||||
"transform"
|
||||
]
|
||||
}
|
||||
|
||||
// @filename: main.ts
|
||||
const a = undefined;
|
||||
Reference in New Issue
Block a user