From 554157174d63246178275485ec064f4680b1ab32 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 4 Aug 2016 15:50:04 -0700 Subject: [PATCH 1/3] Extension loading and management tools --- Gulpfile.ts | 2 +- Jakefile.js | 7 +- src/compiler/commandLineParser.ts | 8 +- src/compiler/core.ts | 90 +++- src/compiler/diagnosticMessages.json | 18 +- src/compiler/extensions.ts | 189 +++++++ src/compiler/performance.ts | 5 +- src/compiler/program.ts | 128 ++--- src/compiler/sys.ts | 4 + src/compiler/tsc.ts | 13 +- src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 11 +- src/harness/extensionRunner.ts | 492 ++++++++++++++++++ src/harness/fourslash.ts | 12 +- src/harness/harness.ts | 7 +- src/harness/harnessLanguageService.ts | 5 +- src/harness/runner.ts | 9 + src/harness/runnerbase.ts | 2 +- src/server/client.ts | 4 + src/server/editorServices.ts | 2 +- src/services/services.ts | 39 +- src/services/shims.ts | 14 +- src/services/tsconfig.json | 1 + .../reportsFailedLoads/test.errors.txt | 8 + .../CompilerHost/reportsFailedLoads/test.js | 5 + .../reportsFailedLoads/test.errors.txt | 8 + .../reportsFailedLoads/test.js | 5 + .../reference/library-reference-12.trace.json | 1 + .../reference/library-reference-2.trace.json | 2 + .../available/extension-api/index.ts | 2 + .../available/extension-api/package.json | 8 + .../cases/extensions/available/tsconfig.json | 9 + .../available/typescript/package.json | 3 + .../scenarios/reportsFailedLoads/test.json | 9 + tests/cases/extensions/source/hello.ts | 1 + 35 files changed, 1016 insertions(+), 108 deletions(-) create mode 100644 src/compiler/extensions.ts create mode 100644 src/harness/extensionRunner.ts create mode 100644 tests/baselines/reference/CompilerHost/reportsFailedLoads/test.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/reportsFailedLoads/test.js create mode 100644 tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.js create mode 100644 tests/cases/extensions/available/extension-api/index.ts create mode 100644 tests/cases/extensions/available/extension-api/package.json create mode 100644 tests/cases/extensions/available/tsconfig.json create mode 100644 tests/cases/extensions/available/typescript/package.json create mode 100644 tests/cases/extensions/scenarios/reportsFailedLoads/test.json create mode 100644 tests/cases/extensions/source/hello.ts diff --git a/Gulpfile.ts b/Gulpfile.ts index 6c91ef52cbb49..2a0e7092931e5 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -411,7 +411,7 @@ gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => { completedDts.pipe(clone()) .pipe(insert.transform((content, file) => { file.path = nodeStandaloneDefinitionsFile; - return content.replace(/declare (namespace|module) ts/g, 'declare module "typescript"'); + return content.replace(/declare (namespace|module) ts {/g, 'declare module "typescript" {\n import * as ts from "typescript";'); })) ]).pipe(gulp.dest(builtLocalDirectory)); }); diff --git a/Jakefile.js b/Jakefile.js index 174be5e702f52..868c3cd53e2ae 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -46,6 +46,7 @@ var compilerSources = [ "declarationEmitter.ts", "emitter.ts", "program.ts", + "extensions.ts", "commandLineParser.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" @@ -67,6 +68,7 @@ var servicesSources = [ "declarationEmitter.ts", "emitter.ts", "program.ts", + "extensions.ts", "commandLineParser.ts", "diagnosticInformationMap.generated.ts" ].map(function (f) { @@ -131,6 +133,7 @@ var harnessCoreSources = [ "typeWriter.ts", "fourslashRunner.ts", "projectsRunner.ts", + "extensionRunner.ts", "loggedIO.ts", "rwcRunner.ts", "test262Runner.ts", @@ -158,7 +161,7 @@ var harnessSources = harnessCoreSources.concat([ "convertCompilerOptionsFromJson.ts", "convertTypingOptionsFromJson.ts", "tsserverProjectSystem.ts", - "matchFiles.ts" + "matchFiles.ts", ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ @@ -524,7 +527,7 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca // Node package definition file to be distributed without the package. Created by replacing // 'ts' namespace with '"typescript"' as a module. - var nodeStandaloneDefinitionsFileContents = definitionFileContents.replace(/declare (namespace|module) ts/g, 'declare module "typescript"'); + var nodeStandaloneDefinitionsFileContents = definitionFileContents.replace(/declare (namespace|module) ts {/g, 'declare module "typescript" {\n import * as ts from "typescript";'); fs.writeFileSync(nodeStandaloneDefinitionsFile, nodeStandaloneDefinitionsFileContents); }); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7e2c6eb8d334e..8dd35b033f031 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -282,6 +282,12 @@ namespace ts { experimental: true, description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators }, + { + name: "extensions", + type: "object", + isTSConfigOnly: true, + description: Diagnostics.List_of_compiler_extensions_to_require + }, { name: "moduleResolution", type: { @@ -429,7 +435,7 @@ namespace ts { name: "strictNullChecks", type: "boolean", description: Diagnostics.Enable_strict_null_checks - } + }, ]; /* @internal */ diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6c87ad82955c3..f12f110e3e39f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2,6 +2,17 @@ /// +namespace ts { + export function startsWith(str: string, prefix: string): boolean { + return str.lastIndexOf(prefix, 0) === 0; + } + + export function endsWith(str: string, suffix: string): boolean { + const expectedPos = str.length - suffix.length; + return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; + } +} + /* @internal */ namespace ts { /** @@ -178,6 +189,26 @@ namespace ts { return array1.concat(array2); } + export function flatten(array1: T[][]): T[] { + if (!array1 || !array1.length) return array1; + return [].concat(...array1); + } + + export function groupBy(array: T[], classifier: (item: T) => string): {[index: string]: T[]}; + export function groupBy(array: T[], classifier: (item: T) => number): {[index: number]: T[]}; + export function groupBy(array: T[], classifier: (item: T) => (string | number)): {[index: string]: T[], [index: number]: T[]} { + if (!array || !array.length) return undefined; + const ret: {[index: string]: T[], [index: number]: T[]} = {}; + for (const elem of array) { + const key = classifier(elem); + if (!ret[key]) { + ret[key] = []; + } + ret[key].push(elem); + } + return ret; + } + export function deduplicate(array: T[], areEqual?: (a: T, b: T) => boolean): T[] { let result: T[]; if (array) { @@ -908,17 +939,6 @@ namespace ts { return true; } - /* @internal */ - export function startsWith(str: string, prefix: string): boolean { - return str.lastIndexOf(prefix, 0) === 0; - } - - /* @internal */ - export function endsWith(str: string, suffix: string): boolean { - const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; - } - export function fileExtensionIs(path: string, extension: string): boolean { return path.length > extension.length && endsWith(path, extension); } @@ -1195,7 +1215,8 @@ namespace ts { export const supportedJavascriptExtensions = [".js", ".jsx"]; const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions); - export function getSupportedExtensions(options?: CompilerOptions): string[] { + export function getSupportedExtensions(options?: CompilerOptions, loadJS?: boolean): string[] { + if (loadJS) return supportedJavascriptExtensions; return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions; } @@ -1373,4 +1394,49 @@ namespace ts { : ((fileName) => fileName.toLowerCase()); } + /** + * This isn't the strictest deep equal, but it's good enough for us + * - +0 === -0 (though who really wants to consider them different?) + * - arguments and arrays can be equal (both typeof === object, both have enumerable keys) + * - doesn't inspect es6 iterables (not that they're used in this code base) + * - doesn't inspect regex toString value (so only references to the same regex are equal) + * - doesn't inspect date primitive number value (so only references to the same date are equal) + */ + export function deepEqual(a: any, b: any, memo?: [any, any][]): boolean { + if (a === b) return true; + if (typeof a !== typeof b) return false; + // Special case NaN + if (typeof a === "number" && isNaN(a) && isNaN(b)) return true; + // We can't know if function arguments are deep equal, so we say they're equal if they look alike + if (typeof a === "object" || typeof a === "function") { + if (memo) { + for (let i = 0; i < memo.length; i++) { + if (memo[i][0] === a && memo[i][1] === b) return true; + if (memo[i][0] === b && memo[i][1] === a) return true; + } + } + else { + memo = []; + } + + const aKeys = ts.getKeys(a); + const bKeys = ts.getKeys(b); + aKeys.sort(); + bKeys.sort(); + + if (aKeys.length !== bKeys.length) return false; + + for (let i = 0; i < aKeys.length; i++) { + if (aKeys[i] !== bKeys[i]) return false; + } + + memo.push([a, b]); + + for (const key of aKeys) { + if (!deepEqual(a[key], b[key], memo)) return false; + } + return true; + } + return false; + } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8126d5c605ea7..2a73331b91b0e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2676,7 +2676,7 @@ "category": "Message", "code": 6099 }, - "'package.json' does not have 'types' field.": { + "'package.json' does not have '{0}' field.": { "category": "Message", "code": 6100 }, @@ -2696,7 +2696,7 @@ "category": "Message", "code": 6104 }, - "Expected type of '{0}' field in 'package.json' to be 'string', got '{1}'.": { + "Expected type of '{0}' field in 'package.json' to be '{1}', got '{2}'.": { "category": "Message", "code": 6105 }, @@ -2824,10 +2824,20 @@ "category": "Message", "code": 6136 }, - "No types specified in 'package.json' but 'allowJs' is set, so returning 'main' value of '{0}'": { + + "List of compiler extensions to require.": { "category": "Message", - "code": 6137 + "code": 6150 + }, + "Extension loading failed with error '{0}'.": { + "category": "Error", + "code": 6151 }, + "Extension '{0}' exported member '{1}' has extension kind '{2}', but was type '{3}' when type '{4}' was expected.": { + "category": "Error", + "code": 6152 + }, + "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/extensions.ts b/src/compiler/extensions.ts new file mode 100644 index 0000000000000..071d3e7f3ec3d --- /dev/null +++ b/src/compiler/extensions.ts @@ -0,0 +1,189 @@ +namespace ts { + + export namespace ExtensionKind { + } + export type ExtensionKind = string; + + export interface ExtensionCollectionMap { + [index: string]: Extension[] | undefined; + } + + export interface ExtensionBase { + name: string; + args: any; + kind: ExtensionKind; + } + + export interface ProfileData { + globalBucket: string; + task: string; + start: number; + length?: number; + } + + export type Extension = ExtensionBase; + + export interface ExtensionCache { + getCompilerExtensions(): ExtensionCollectionMap; + getExtensionLoadingDiagnostics(): Diagnostic[]; + } + + export interface ExtensionHost extends ModuleResolutionHost { + loadExtension?(name: string): any; + } + + export interface Program { + /** + * Gets a map of loaded compiler extensions + */ + getCompilerExtensions(): ExtensionCollectionMap; + + /** + * Gets only diagnostics reported while loading extensions + */ + getExtensionLoadingDiagnostics(): Diagnostic[]; + } + + /* @internal */ + export interface TypeCheckerHost { + getCompilerExtensions(): ExtensionCollectionMap; + } + + export const perfTraces: Map = {}; + + function getExtensionRootName(qualifiedName: string) { + return qualifiedName.substring(0, qualifiedName.indexOf("[")) || qualifiedName; + } + + function createTaskName(qualifiedName: string, task: string) { + return `${task}|${qualifiedName}`; + } + + export function startProfile(enabled: boolean, key: string, bucket?: string) { + if (!enabled) return; + performance.emit(`start|${key}`); + perfTraces[key] = { + task: key, + start: performance.mark(), + length: undefined, + globalBucket: bucket + }; + } + + export function completeProfile(enabled: boolean, key: string) { + if (!enabled) return; + Debug.assert(!!perfTraces[key], "Completed profile did not have a corresponding start."); + perfTraces[key].length = performance.measure(perfTraces[key].globalBucket, perfTraces[key].start); + performance.emit(`end|${key}`); + } + + export function startExtensionProfile(enabled: boolean, qualifiedName: string, task: string) { + if (!enabled) return; + const longTask = createTaskName(qualifiedName, task); + startProfile(/*enabled*/true, longTask, getExtensionRootName(qualifiedName)); + } + + export function completeExtensionProfile(enabled: boolean, qualifiedName: string, task: string) { + if (!enabled) return; + const longTask = createTaskName(qualifiedName, task); + completeProfile(/*enabled*/true, longTask); + } + + export function createExtensionCache(options: CompilerOptions, host: ExtensionHost, resolvedExtensionNames?: Map): ExtensionCache { + + const diagnostics: Diagnostic[] = []; + const extOptions = options.extensions; + const extensionNames = (extOptions instanceof Array) ? extOptions : getKeys(extOptions); + // Eagerly evaluate extension paths, but lazily execute their contents + resolvedExtensionNames = resolvedExtensionNames || resolveExtensionNames(); + let extensions: ExtensionCollectionMap; + + const cache: ExtensionCache = { + getCompilerExtensions: () => { + if (!extensions) { + extensions = collectCompilerExtensions(); + } + return extensions; + }, + getExtensionLoadingDiagnostics: () => { + // To get extension loading diagnostics, we need to make sure we've actually loaded them + cache.getCompilerExtensions(); + return diagnostics; + }, + }; + return cache; + + function resolveExtensionNames(): Map { + const currentDirectory = host.getCurrentDirectory ? host.getCurrentDirectory() : ""; + const extMap: Map = {}; + forEach(extensionNames, name => { + const resolved = resolveModuleName(name, combinePaths(currentDirectory, "tsconfig.json"), options, host, /*loadJs*/true).resolvedModule; + if (resolved) { + extMap[name] = resolved.resolvedFileName; + } + }); + return extMap; + } + + function collectCompilerExtensions(): ExtensionCollectionMap { + const profilingEnabled = options.extendedDiagnostics; + const extensionLoadResults = map(extensionNames, (name) => { + const resolved = resolvedExtensionNames[name]; + let result: any; + let error: any; + if (!resolved) { + error = new Error(`Host could not locate extension '${name}'.`); + } + if (resolved && host.loadExtension) { + try { + startProfile(profilingEnabled, name, name); + result = host.loadExtension(resolved); + completeProfile(profilingEnabled, name); + } + catch (e) { + error = e; + } + } + else if (!host.loadExtension) { + error = new Error("Extension loading not implemented in host!"); + } + if (error) { + diagnostics.push(createCompilerDiagnostic(Diagnostics.Extension_loading_failed_with_error_0, error)); + } + return { name, result, error }; + }); + const successfulExtensionLoadResults = filter(extensionLoadResults, res => !res.error); + const preparedExtensionObjects = map(successfulExtensionLoadResults, res => { + if (!res.result) { + return []; + } + const aggregate: Extension[] = []; + forEachKey(res.result, key => { + const potentialExtension = res.result[key]; + if (!potentialExtension) { + return; // Avoid errors on explicitly exported null/undefined (why would someone do that, though?) + } + const annotatedKind = potentialExtension["extension-kind"]; + if (typeof annotatedKind !== "string") { + return; + } + const ext: ExtensionBase = { + name: key !== "default" ? `${res.name}[${key}]` : res.name, + args: extensionNames === extOptions ? undefined : (extOptions as Map)[res.name], + kind: annotatedKind as ExtensionKind, + }; + switch (ext.kind) { + default: + // Include a default case which just puts the extension unchecked onto the base extension + // This can allow language service extensions to query for custom extension kinds + (ext as any).__extension = potentialExtension; + break; + } + aggregate.push(ext as Extension); + }); + return aggregate; + }); + return groupBy(flatten(preparedExtensionObjects), elem => elem.kind) || {}; + } + } +} \ No newline at end of file diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 89db876ae5e48..71f3dbd74854d 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -58,10 +58,11 @@ namespace ts.performance { * @param measureName The name of the performance measurement. * @param marker The timestamp of the starting mark. */ - export function measure(measureName: string, marker: number) { + export function measure(measureName: string, marker: number): number { if (measures) { - measures[measureName] = (getProperty(measures, measureName) || 0) + (timestamp() - marker); + return measures[measureName] = (getProperty(measures, measureName) || 0) + (timestamp() - marker); } + return 0; } /** diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7d40b2f3219fc..9ba91f41bac81 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,6 +1,8 @@ /// /// /// +/// + namespace ts { /** The version of the TypeScript compiler release */ @@ -118,57 +120,51 @@ namespace ts { skipTsx: boolean; } - function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string { - let jsonContent: { typings?: string, types?: string, main?: string }; - try { - const jsonText = state.host.readFile(packageJsonPath); - jsonContent = jsonText ? <{ typings?: string, types?: string, main?: string }>JSON.parse(jsonText) : {}; + function getPackageEntry(packageJson: any, key: string, tag: string, state: ModuleResolutionState) { + const value = packageJson[key]; + if (typeof value === tag) { + return value; } - catch (e) { - // gracefully handle if readFile fails or returns not JSON - jsonContent = {}; + if (state.traceEnabled) { + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, key, tag, typeof value); } + return undefined; + } - let typesFile: string; - let fieldName: string; - // first try to read content of 'typings' section (backward compatibility) - if (jsonContent.typings) { - if (typeof jsonContent.typings === "string") { - fieldName = "typings"; - typesFile = jsonContent.typings; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "typings", typeof jsonContent.typings); - } - } + function getPackageEntryAsPath(packageJson: any, packageJsonPath: string, key: string, state: ModuleResolutionState) { + const value = getPackageEntry(packageJson, key, "string", state); + const path = value ? normalizePath(combinePaths(getDirectoryPath(packageJsonPath), value)) : undefined; + if (path && state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, key, value, path); } - // then read 'types' - if (!typesFile && jsonContent.types) { - if (typeof jsonContent.types === "string") { - fieldName = "types"; - typesFile = jsonContent.types; - } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, "types", typeof jsonContent.types); - } - } + return path; + } + + function getPackageTypes(packageJsonPath: string, state: ModuleResolutionState) { + const { config } = readConfigFile(packageJsonPath, state.host.readFile); + if (config) { + return getPackageEntryAsPath(config, packageJsonPath, "typings", state) + || getPackageEntryAsPath(config, packageJsonPath, "types", state) + // Use the main module for inferring types if no types package specified and the allowJs is set + || (state.compilerOptions.allowJs && getPackageEntryAsPath(config, packageJsonPath, "main", state)); } - if (typesFile) { - const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile)); + else { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath); + trace(state.host, Diagnostics.package_json_does_not_have_0_field, "types"); } - return typesFilePath; } - // Use the main module for inferring types if no types package specified and the allowJs is set - if (state.compilerOptions.allowJs && jsonContent.main && typeof jsonContent.main === "string") { + return undefined; + } + + function getPackageMain(packageJsonPath: string, state: ModuleResolutionState) { + const { config } = readConfigFile(packageJsonPath, state.host.readFile); + if (config) { + return getPackageEntryAsPath(config, packageJsonPath, "main", state); + } + else { if (state.traceEnabled) { - trace(state.host, Diagnostics.No_types_specified_in_package_json_but_allowJs_is_set_so_returning_main_value_of_0, jsonContent.main); + trace(state.host, Diagnostics.package_json_does_not_have_0_field, "main"); } - const mainFilePath = normalizePath(combinePaths(baseDirectory, jsonContent.main)); - return mainFilePath; } return undefined; } @@ -293,7 +289,7 @@ namespace ts { }; } - export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, loadJs?: boolean): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); if (traceEnabled) { trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); @@ -315,7 +311,7 @@ namespace ts { let result: ResolvedModuleWithFailedLookupLocations; switch (moduleResolution) { case ModuleResolutionKind.NodeJs: - result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host); + result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, loadJs); break; case ModuleResolutionKind.Classic: result = classicNameResolver(moduleName, containingFile, compilerOptions, host); @@ -610,7 +606,7 @@ namespace ts { }; } - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, loadJs?: boolean): ResolvedModuleWithFailedLookupLocations { const containingDirectory = getDirectoryPath(containingFile); const supportedExtensions = getSupportedExtensions(compilerOptions); const traceEnabled = isTraceEnabled(compilerOptions, host); @@ -626,7 +622,7 @@ namespace ts { if (traceEnabled) { trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName); } - resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state); + resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state, loadJs); isExternalLibraryImport = resolvedFileName !== undefined; } else { @@ -716,25 +712,20 @@ namespace ts { } } - function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string { + function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState, loadJS?: boolean): string { const packageJsonPath = combinePaths(candidate, "package.json"); const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host); if (directoryExists && state.host.fileExists(packageJsonPath)) { if (state.traceEnabled) { trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath); } - const typesFile = tryReadTypesSection(packageJsonPath, candidate, state); + const typesFile = loadJS ? getPackageMain(packageJsonPath, state) : getPackageTypes(packageJsonPath, state); if (typesFile) { const result = loadModuleFromFile(typesFile, extensions, failedLookupLocation, !directoryProbablyExists(getDirectoryPath(typesFile), state.host), state); if (result) { return result; } } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_types_field); - } - } } else { if (state.traceEnabled) { @@ -747,30 +738,31 @@ namespace ts { return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state); } - function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { + function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, loadJS?: boolean): string { const nodeModulesFolder = combinePaths(directory, "node_modules"); const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName)); - const supportedExtensions = getSupportedExtensions(state.compilerOptions); + + const supportedExtensions = getSupportedExtensions(state.compilerOptions, loadJS); let result = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, !nodeModulesFolderExists, state); if (result) { return result; } - result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state); + result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state, loadJS); if (result) { return result; } } - function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { + function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, loadJS?: boolean): string { directory = normalizeSlashes(directory); while (true) { const baseName = getBaseFileName(directory); if (baseName !== "node_modules") { // Try to load source from the package - const packageResult = loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state); - if (packageResult && hasTypeScriptFileExtension(packageResult)) { + const packageResult = loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state, loadJS); + if (packageResult && (hasTypeScriptFileExtension(packageResult) || loadJS)) { // Always prefer a TypeScript (.ts, .tsx, .d.ts) file shipped with the package return packageResult; } @@ -954,6 +946,7 @@ namespace ts { const newLine = getNewLineCharacter(options); const realpath = sys.realpath && ((path: string) => sys.realpath(path)); + const loadExtension = sys.loadExtension && ((name: string) => sys.loadExtension(name)); return { getSourceFile, @@ -969,7 +962,8 @@ namespace ts { trace: (s: string) => sys.write(s + newLine), directoryExists: directoryName => sys.directoryExists(directoryName), getDirectories: (path: string) => sys.getDirectories(path), - realpath + realpath, + loadExtension }; } @@ -1004,7 +998,8 @@ namespace ts { } const category = DiagnosticCategory[diagnostic.category].toLowerCase(); - output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()) }${ host.getNewLine() }`; + const code = typeof diagnostic.code === "string" ? diagnostic.code : `TS${ diagnostic.code }`; + output += `${ category } ${ code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()) }${ host.getNewLine() }`; } return output; } @@ -1087,7 +1082,7 @@ namespace ts { return result; } - export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program { + export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, extensionCache?: ExtensionCache): Program { let program: Program; let files: SourceFile[] = []; let commonSourceDirectory: string; @@ -1187,6 +1182,8 @@ namespace ts { // unconditionally set oldProgram to undefined to prevent it from being captured in closure oldProgram = undefined; + extensionCache = extensionCache || createExtensionCache(options, host); + program = { getRootFileNames: () => rootNames, getSourceFile, @@ -1209,7 +1206,13 @@ namespace ts { getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(), getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), getFileProcessingDiagnostics: () => fileProcessingDiagnostics, - getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives + getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, + getCompilerExtensions() { + return extensionCache.getCompilerExtensions(); + }, + getExtensionLoadingDiagnostics() { + return extensionCache.getExtensionLoadingDiagnostics(); + }, }; verifyCompilerOptions(); @@ -1753,6 +1756,7 @@ namespace ts { const allDiagnostics: Diagnostic[] = []; addRange(allDiagnostics, fileProcessingDiagnostics.getGlobalDiagnostics()); addRange(allDiagnostics, programDiagnostics.getGlobalDiagnostics()); + allDiagnostics.push(...extensionCache.getExtensionLoadingDiagnostics()); return sortAndDeduplicateDiagnostics(allDiagnostics); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 29ae2c60af165..251c22fd43ade 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -32,6 +32,7 @@ namespace ts { getMemoryUsage?(): number; exit(exitCode?: number): void; realpath?(path: string): string; + loadExtension?(name: string): any; } export interface FileWatcher { @@ -548,6 +549,9 @@ namespace ts { }, realpath(path: string): string { return _fs.realpathSync(path); + }, + loadExtension(name) { + return require(name); } }; return nodeSystem; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 10538d0c009ee..9ecde9262f5f2 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -607,13 +607,18 @@ namespace ts { // First get and report any syntactic errors. diagnostics = program.getSyntacticDiagnostics(); + // Count warnings/messages and ignore them for determining continued error reporting + const nonErrorCount = countWhere(diagnostics, d => d.category !== DiagnosticCategory.Error); + // If we didn't have any syntactic errors, then also try getting the global and // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + if (diagnostics.length === nonErrorCount) { + diagnostics = diagnostics.concat(program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics())); + + const nonErrorCount = countWhere(diagnostics, d => d.category !== DiagnosticCategory.Error); - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics(); + if (diagnostics.length === nonErrorCount) { + diagnostics = diagnostics.concat(program.getSemanticDiagnostics()); } } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index cc9bfddcece78..9d98da46cd1a1 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -24,6 +24,7 @@ "declarationEmitter.ts", "emitter.ts", "program.ts", + "extensions.ts", "commandLineParser.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28ededebb0de1..dc750e2c9dff5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2540,7 +2540,7 @@ namespace ts { length: number; messageText: string | DiagnosticMessageChain; category: DiagnosticCategory; - code: number; + code: number | string; } export enum DiagnosticCategory { @@ -2633,6 +2633,7 @@ namespace ts { typeRoots?: string[]; /*@internal*/ version?: boolean; /*@internal*/ watch?: boolean; + extensions?: string[] | Map; [option: string]: CompilerOptionsValue | undefined; } @@ -2966,6 +2967,14 @@ namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + + /** + * Delegates the loading of compiler extensions to the compiler host. + * The function should return the result of executing the code of an extension + * - its exported members. These members will be searched for objects who have been decorated with + * specific flags. + */ + loadExtension?(extension: string): any; } export interface TextSpan { diff --git a/src/harness/extensionRunner.ts b/src/harness/extensionRunner.ts new file mode 100644 index 0000000000000..ca7ab8c4a6d9b --- /dev/null +++ b/src/harness/extensionRunner.ts @@ -0,0 +1,492 @@ +/// +/// +/// + +interface ExtensionTestConfig { + inputFiles: string[]; // Files from the source directory to include in the compilation + fourslashTest?: string; // File from the fourslash directory to test this compilation with + availableExtensions: string[]; // Extensions from the available directory to make available to the test + compilerOptions?: ts.CompilerOptions; // Optional compiler options to run with (usually at least "extensions" is specified) +} + +type VirtualCompilationFunction = (files: string[], options: ts.CompilerOptions) => Harness.Compiler.CompilerResult; + +class ExtensionRunner extends RunnerBase { + private basePath = "tests/cases/extensions"; + private scenarioPath = ts.combinePaths(this.basePath, "scenarios"); + private extensionPath = ts.combinePaths(this.basePath, "available"); + private sourcePath = ts.combinePaths(this.basePath, "source"); + private fourslashPath = ts.combinePaths(this.basePath, "fourslash"); + private extensionAPI: ts.Map = {}; + private extensions: ts.Map> = {}; + private virtualLib: ts.Map = {}; + private virtualFs: ts.Map = {}; + + prettyPrintDiagnostic(diagnostic: ts.Diagnostic): string { + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); + if (diagnostic.file) { + const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`; + } + else { + return `!!!${message}`; + } + } + + private innerCanonicalName = ts.createGetCanonicalFileName(true); + private getCanonicalFileName = (fileName: string) => ts.toPath(fileName, "/", this.innerCanonicalName); + + loadSetIntoFsAt(set: ts.Map, prefix: string) { + ts.Debug.assert(!!prefix, "Prefix must exist"); + ts.Debug.assert(set !== this.virtualFs, "You cannot try to load the fs into itself."); + + // Load a fileset at the given location, but exclude the 'lib' kind files from the added set (they'll be reloaded at the top level before compilation) + ts.forEachKey(set, key => ts.forEachKey(this.virtualLib, path => key === path) ? void 0 : void (this.virtualFs[this.getCanonicalFileName(`${prefix}/${key}`)] = set[key])); + } + + loadSetIntoFs(set: ts.Map) { + ts.Debug.assert(set !== this.virtualFs, "You cannot try to load the fs into itself."); + ts.forEachKey(set, key => void (this.virtualFs[this.getCanonicalFileName(key)] = set[key])); + } + + private traces: string[] = []; + private mockHost: ts.CompilerHost = { + useCaseSensitiveFileNames: () => true, + getNewLine: () => "\n", + readFile: (path) => this.virtualFs[this.mockHost.getCanonicalFileName(path)], + writeFile: (path, content, foo, bar, baz) => { + this.virtualFs[this.mockHost.getCanonicalFileName(path)] = content; + }, + fileExists: (path) => { + return !!this.virtualFs[this.mockHost.getCanonicalFileName(path)]; + }, + directoryExists: (path) => { + const fullPath = this.mockHost.getCanonicalFileName(path); + return ts.forEach(ts.getKeys(this.virtualFs), key => ts.startsWith(key, fullPath)); + }, + getCurrentDirectory(): string { return "/"; }, + getSourceFile: (path, languageVersion, onError): ts.SourceFile => { + const fullPath = this.mockHost.getCanonicalFileName(path); + return ts.createSourceFile(fullPath, this.virtualFs[fullPath], languageVersion); + }, + getDefaultLibLocation: () => "/lib/", + getDefaultLibFileName: (options) => { + return ts.combinePaths(this.mockHost.getDefaultLibLocation(), ts.getDefaultLibFileName(options)); + }, + getCanonicalFileName: this.getCanonicalFileName, + getDirectories: (path) => { + path = this.mockHost.getCanonicalFileName(path); + return ts.filter(ts.map(ts.filter(ts.getKeys(this.virtualFs), + fullpath => ts.startsWith(fullpath, path) && fullpath.substr(path.length, 1) === "/"), + fullpath => fullpath.substr(path.length + 1).indexOf("/") >= 0 ? fullpath.substr(0, 1 + path.length + fullpath.substr(path.length + 1).indexOf("/")) : fullpath), + fullpath => fullpath.lastIndexOf(".") === -1); + }, + loadExtension: (path) => this.mockLoadExtension(path), + trace: (s) => { + this.traces.push(s); + } + }; + + mockLoadExtension(path: string) { + const fullPath = this.getCanonicalFileName(path); + const m = { exports: {} }; + ((module, exports, require) => { eval(this.virtualFs[fullPath]); })( + m, + m.exports, + (name: string) => { + return this.mockLoadExtension( + this.getCanonicalFileName( + ts.resolveModuleName(name, fullPath, { module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.NodeJs }, this.mockHost, true).resolvedModule.resolvedFileName + ) + ); + } + ); + return m.exports; + } + + makeLSMockAdapter(files: string[], options: ts.CompilerOptions, token?: ts.HostCancellationToken) { + const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options); + // The host returned by the harness is _mostly_ suitable for use here + // it just needs to be monkeypatched to load extensions, report directories, and canonicalize script paths + const host = adapter.getHost(); + host.getDefaultLibFileName = () => "/lib/lib.d.ts"; + host.getCurrentDirectory = () => "/"; + (host as ts.LanguageServiceHost).loadExtension = (path) => this.mockLoadExtension(path); + (host as ts.LanguageServiceHost).useCaseSensitiveFileNames = () => true; + host.trace = (s) => { + this.traces.push(s); + }; + host.getScriptInfo = (fileName: string) => { + fileName = this.getCanonicalFileName(fileName); + return ts.lookUp(host.fileNameToScript, fileName); + }; + host.getDirectories = (s: string) => this.mockHost.getDirectories(s); + host.addScript = (fileName: string, content: string, isRootFile: boolean): void => { + const canonical = this.getCanonicalFileName(fileName); + host.fileNameToScript[canonical] = new Harness.LanguageService.ScriptInfo(canonical, content, isRootFile); + }; + ts.forEach(files, file => { + host.addScript(file, this.virtualFs[file], looksLikeRootFile(file)); + }); + + return adapter; + + function looksLikeRootFile(file: string) { + return ts.endsWith(file, ".ts") && !ts.endsWith(file, ".d.ts") && (file.indexOf("node_modules") === -1); + } + } + + makeMockLSHost(files: string[], options: ts.CompilerOptions) { + const adapter = this.makeLSMockAdapter(files, options); + return adapter.getHost(); + }; + + getTraces(): string[] { + const traces = this.traces; + this.traces = []; + return traces.map(t => t.replace(/\([0-9\.e\+\-]+ ms\)$/, "(REDACTED ms)")); + } + + languageServiceCompile(typescriptFiles: string[], options: ts.CompilerOptions): Harness.Compiler.CompilerResult { + const self = this; + const host = this.makeMockLSHost(ts.getKeys(this.virtualFs), options); + const service = ts.createLanguageService(host); + const fileResults: Harness.Compiler.GeneratedFile[] = []; + + const diagnostics = ts.concatenate(ts.concatenate( + service.getProgramDiagnostics(), + ts.flatten(ts.map(typescriptFiles, fileName => service.getSyntacticDiagnostics(this.getCanonicalFileName(fileName))))), + ts.flatten(ts.map(typescriptFiles, fileName => service.getSemanticDiagnostics(this.getCanonicalFileName(fileName))))); + + const emitResult = service.getProgram().emit(/*targetSourceFile*/undefined, writeFile); + + const allDiagnostics = ts.sortAndDeduplicateDiagnostics(ts.concatenate(diagnostics, emitResult.diagnostics)); + + return new Harness.Compiler.CompilerResult(fileResults, allDiagnostics, /*program*/undefined, host.getCurrentDirectory(), emitResult.sourceMaps, this.getTraces()); + + function writeFile(fileName: string, code: string, writeByteOrderMark: boolean, onError: (message: string) => void, sourceFiles: ts.SourceFile[]) { + fileResults.push({ + fileName, + writeByteOrderMark, + code + }); + self.mockHost.writeFile(fileName, code, writeByteOrderMark, onError, sourceFiles); + } + } + + programCompile(typescriptFiles: string[], options: ts.CompilerOptions): Harness.Compiler.CompilerResult { + const self = this; + const program = ts.createProgram(typescriptFiles, options, this.mockHost); + const fileResults: Harness.Compiler.GeneratedFile[] = []; + const diagnostics = ts.getPreEmitDiagnostics(program); + const emitResult = program.emit(/*targetSourceFile*/undefined, writeFile); + + const allDiagnostics = ts.sortAndDeduplicateDiagnostics(ts.concatenate(diagnostics, emitResult.diagnostics)); + + return new Harness.Compiler.CompilerResult(fileResults, allDiagnostics, /*program*/undefined, this.mockHost.getCurrentDirectory(), emitResult.sourceMaps, this.getTraces()); + function writeFile(fileName: string, code: string, writeByteOrderMark: boolean, onError: (message: string) => void, sourceFiles: ts.SourceFile[]) { + fileResults.push({ + fileName, + writeByteOrderMark, + code + }); + self.mockHost.writeFile(fileName, code, writeByteOrderMark, onError, sourceFiles); + } + } + + compile(fileset: ts.Map, options: ts.CompilerOptions, compileFunc: VirtualCompilationFunction): Harness.Compiler.CompilerResult { + this.loadSetIntoFs(this.virtualLib); + this.loadSetIntoFs(fileset); + + // Consider all TS files in the passed fileset as the root files, but not any under a node_modules folder + const typescriptFiles = ts.filter(ts.getKeys(fileset), name => ts.endsWith(name, ".ts") && !(name.indexOf("node_modules") >= 0)); + return compileFunc(typescriptFiles, options); + } + + buildMap(compileFunc: VirtualCompilationFunction, map: ts.Map, out: ts.Map, compilerOptions?: ts.CompilerOptions, shouldError?: boolean): Harness.Compiler.CompilerResult { + const results = this.compile(map, compilerOptions ? compilerOptions : { module: ts.ModuleKind.CommonJS, declaration: true }, compileFunc); + const diagnostics = results.errors; + if (shouldError && diagnostics && diagnostics.length) { + for (let i = 0; i < diagnostics.length; i++) { + console.log(this.prettyPrintDiagnostic(diagnostics[i])); + } + throw new Error("Compiling test harness extension API code resulted in errors."); + } + ts.copyMap(this.virtualFs, out); + this.virtualFs = {}; + return results; + } + + private loadExtensions() { + this.extensionAPI = { + "package.json": Harness.IO.readFile(ts.combinePaths(this.extensionPath, "extension-api/package.json")), + "index.ts": Harness.IO.readFile(ts.combinePaths(this.extensionPath, "extension-api/index.ts")), + }; + this.buildMap((str, opts) => this.programCompile(str, opts), this.extensionAPI, this.extensionAPI, { module: ts.ModuleKind.CommonJS, declaration: true }, /*shouldError*/true); + + ts.forEach(Harness.IO.getDirectories(this.extensionPath), path => { + if (path === "extension-api" || path === "typescript") return; // Since these are dependencies of every actual test extension, we handle them specially + const packageDir = ts.combinePaths(this.extensionPath, path); + const extensionFileset: ts.Map = {}; + const extensionFiles = this.enumerateFiles(packageDir, /*regex*/ undefined, { recursive: true }); + ts.forEach(extensionFiles, name => { + const shortName = name.substring(packageDir.length + 1); + extensionFileset[shortName] = Harness.IO.readFile(name); + }); + this.loadSetIntoFsAt(this.extensionAPI, "/node_modules/extension-api"); + + this.buildMap((str, opts) => this.programCompile(str, opts), extensionFileset, extensionFileset, { module: ts.ModuleKind.CommonJS, declaration: true }, /*shouldError*/true); + this.extensions[path] = extensionFileset; + }); + } + + constructor() { + super(); + const {content: libContent} = Harness.getDefaultLibraryFile(Harness.IO); + const tsLibContents = Harness.IO.readFile("built/local/typescript.d.ts"); + this.virtualLib = { + "/lib/lib.d.ts": libContent, + "/node_modules/typescript/index.d.ts": tsLibContents + }; + this.loadExtensions(); + } + + kind(): "extension" { + return "extension"; + } + + enumerateTestFiles(): string[] { + return this.enumerateFiles(this.scenarioPath, /\.json$/, { recursive: true }); + } + + /** Setup the runner's tests so that they are ready to be executed by the harness + * The first test should be a describe/it block that sets up the harness's compiler instance appropriately + */ + public initializeTests(): void { + describe("Compiler Extensions", () => { + if (this.tests.length === 0) { + const testFiles = this.enumerateTestFiles(); + testFiles.forEach(fn => { + this.runTest(fn); + }); + } + else { + this.tests.forEach(test => this.runTest(test)); + } + }); + } + + getByteOrderMarkText(file: Harness.Compiler.GeneratedFile): string { + return file.writeByteOrderMark ? "\u00EF\u00BB\u00BF" : ""; + } + + private compileTargets: [string, VirtualCompilationFunction][] = [["CompilerHost", (str, opts) => this.programCompile(str, opts)], ["LanguageServiceHost", (str, opts) => this.languageServiceCompile(str, opts)]]; + /** + * Extensions tests are complete end-to-end tests with multiple compilations to prepare a test + * + * Tests need to be: + * Run under both `compilerHost` and `languageServiceHost` environments + * - When under LSHost, verify all fourslash test-type results included in the test + * - Verify output baseline + * - Verify error baseline + * - Verify sourcemaps if need be + * - Verify traces if need be + */ + private runTest(caseName: string) { + const caseNameNoExtension = caseName.replace(/\.json$/, ""); + describe(caseNameNoExtension, () => { + let shortCasePath: string; + let testConfigText: string; + let testConfig: ExtensionTestConfig; + let inputSources: ts.Map; + let inputTestFiles: Harness.Compiler.TestFile[]; + before(() => { + shortCasePath = caseName.substring(this.scenarioPath.length + 1).replace(/\.json$/, ""); + testConfigText = Harness.IO.readFile(caseName); + testConfig = JSON.parse(testConfigText); + inputSources = {}; + inputTestFiles = []; + ts.forEach(testConfig.inputFiles, name => { + inputSources[name] = Harness.IO.readFile(ts.combinePaths(this.sourcePath, name)); + inputTestFiles.push({ + unitName: this.getCanonicalFileName(name), + content: inputSources[name] + }); + }); + }); + + after(() => { + shortCasePath = undefined; + testConfigText = undefined; + testConfig = undefined; + inputSources = undefined; + inputTestFiles = undefined; + }); + + ts.forEach(this.compileTargets, ([name, compileCb]) => { + describe(`${name}`, () => { + let sources: ts.Map; + let result: Harness.Compiler.CompilerResult; + before(() => { + this.traces = []; // Clear out any traces from tests which made traces, but didn't specify traceResolution + this.virtualFs = {}; // In case a fourslash test was run last (which doesn't clear FS on end like buildMap does), clear the FS + sources = {}; + ts.copyMap(inputSources, sources); + ts.forEach(testConfig.availableExtensions, ext => this.loadSetIntoFsAt(this.extensions[ext], `/node_modules/${ext}`)); + result = this.buildMap(compileCb, sources, sources, testConfig.compilerOptions, /*shouldError*/false); + }); + + after(() => { + sources = undefined; + result = undefined; + }); + + const errorsTestName = `Correct errors`; + it(errorsTestName, () => { + Harness.Baseline.runBaseline(errorsTestName, `${name}/${shortCasePath}.errors.txt`, () => { + /* tslint:disable:no-null-keyword */ + if (result.errors.length === 0) return null; + /* tslint:enable:no-null-keyword */ + return Harness.Compiler.getErrorBaseline(inputTestFiles, result.errors); + }); + }); + + const traceTestName = `Correct traces`; + it(traceTestName, () => { + if (!(testConfig.compilerOptions.traceResolution)) { + return; + } + Harness.Baseline.runBaseline(traceTestName, `${name}/${shortCasePath}.trace.txt`, (): string => { + return (result.traceResults || []).join("\n"); + }); + }); + + const sourcemapTestName = `Correct sourcemap content`; + it(sourcemapTestName, () => { + if (!(testConfig.compilerOptions.sourceMap || testConfig.compilerOptions.inlineSourceMap)) { + return; + } + Harness.Baseline.runBaseline(sourcemapTestName, `${name}/${shortCasePath}.sourcemap.txt`, () => { + const record = result.getSourceMapRecord(); + if (testConfig.compilerOptions.noEmitOnError && result.errors.length !== 0 && record === undefined) { + // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. + /* tslint:disable:no-null-keyword */ + return null; + /* tslint:enable:no-null-keyword */ + } + return record; + }); + }); + + const sourcemapOutputTestName = `Correct sourcemap output`; + it(sourcemapOutputTestName, () => { + if (testConfig.compilerOptions.inlineSourceMap) { + if (result.sourceMaps.length > 0) { + throw new Error("No sourcemap files should be generated if inlineSourceMaps was set."); + } + return; + } + else if (!testConfig.compilerOptions.sourceMap) { + return; + } + if (result.sourceMaps.length !== result.files.length) { + throw new Error("Number of sourcemap files should be same as js files."); + } + + Harness.Baseline.runBaseline(sourcemapOutputTestName, `${name}/${shortCasePath}.js.map`, () => { + if (testConfig.compilerOptions.noEmitOnError && result.errors.length !== 0 && result.sourceMaps.length === 0) { + // We need to return null here or the runBaseLine will actually create a empty file. + // Baselining isn't required here because there is no output. + /* tslint:disable:no-null-keyword */ + return null; + /* tslint:enable:no-null-keyword */ + } + + let sourceMapCode = ""; + for (let i = 0; i < result.sourceMaps.length; i++) { + sourceMapCode += "//// [" + Harness.Path.getFileName(result.sourceMaps[i].fileName) + "]\r\n"; + sourceMapCode += this.getByteOrderMarkText(result.sourceMaps[i]); + sourceMapCode += result.sourceMaps[i].code; + } + + return sourceMapCode; + }); + }); + + const emitOutputTestName = `Correct emit (JS/DTS)`; + it(emitOutputTestName, () => { + if (!ts.forEach(testConfig.inputFiles, name => !ts.endsWith(name, ".d.ts"))) { + return; + } + if (!testConfig.compilerOptions.noEmit && result.files.length === 0 && result.errors.length === 0) { + throw new Error("Expected at least one js file to be emitted or at least one error to be created."); + } + + // check js output + Harness.Baseline.runBaseline(emitOutputTestName, `${name}/${shortCasePath}.js`, () => { + let tsCode = ""; + const tsSources = inputTestFiles; + if (tsSources.length > 1) { + tsCode += "//// [" + caseNameNoExtension + "] ////\r\n\r\n"; + } + for (let i = 0; i < tsSources.length; i++) { + tsCode += "//// [" + Harness.Path.getFileName(tsSources[i].unitName) + "]\r\n"; + tsCode += tsSources[i].content + (i < (tsSources.length - 1) ? "\r\n" : ""); + } + + let jsCode = ""; + for (let i = 0; i < result.files.length; i++) { + jsCode += "//// [" + Harness.Path.getFileName(result.files[i].fileName) + "]\r\n"; + jsCode += this.getByteOrderMarkText(result.files[i]); + jsCode += result.files[i].code; + } + + if (result.declFilesCode.length > 0) { + jsCode += "\r\n\r\n"; + for (let i = 0; i < result.declFilesCode.length; i++) { + jsCode += "//// [" + Harness.Path.getFileName(result.declFilesCode[i].fileName) + "]\r\n"; + jsCode += this.getByteOrderMarkText(result.declFilesCode[i]); + jsCode += result.declFilesCode[i].code; + } + } + + if (jsCode.length > 0) { + return tsCode + "\r\n\r\n" + jsCode; + } + else { + /* tslint:disable:no-null-keyword */ + return null; + /* tslint:enable:no-null-keyword */ + } + }); + }); + }); + }); + + it("passes fourslash verification", () => { + if (testConfig.fourslashTest) { + this.virtualFs = {}; + const testFile = `${this.fourslashPath}/${testConfig.fourslashTest}`; + let testFileContents = Harness.IO.readFile(testFile); + testFileContents = testFileContents.replace(`/// `, ""); + const testContent = [`/// `, ""]; + ts.forEach(inputTestFiles, testFile => { + testContent.push(`// @Filename: ${testFile.unitName.substring(1)}`); // Drop leading / + testContent.push(...testFile.content.split("\n").map(s => `////${s}`)); + }); + testContent.push("// @Filename: tsconfig.json"); + testContent.push(`////${JSON.stringify(testConfig.compilerOptions)}`); + testContent.push(testFileContents); + const finishedTestContent = testContent.join("\n"); + + this.loadSetIntoFs(this.virtualLib); + ts.forEach(testConfig.availableExtensions, ext => this.loadSetIntoFsAt(this.extensions[ext], `/node_modules/${ext}`)); + + const adapterFactory = (token: ts.HostCancellationToken) => this.makeLSMockAdapter(ts.getKeys(this.virtualFs), testConfig.compilerOptions, token); + + FourSlash.runFourSlashTestContent(shortCasePath, adapterFactory, finishedTestContent, testFile); + } + }); + }); + } +} \ No newline at end of file diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc60909..14f16eb09f2d6 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -242,7 +242,7 @@ namespace FourSlash { } } - constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { + constructor(private basePath: string, private testType: FourSlashTestType | ((token: ts.HostCancellationToken) => Harness.LanguageService.LanguageServiceAdapter), public testData: FourSlashData) { // Create a new Services Adapter this.cancellationToken = new TestCancellationToken(); const compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); @@ -250,7 +250,13 @@ namespace FourSlash { compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath)); } - const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); + let languageServiceAdapter: Harness.LanguageService.LanguageServiceAdapter; + if (typeof testType === "number") { + languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); + } + else { + languageServiceAdapter = testType(this.cancellationToken); + } this.languageServiceAdapterHost = languageServiceAdapter.getHost(); this.languageService = languageServiceAdapter.getLanguageService(); @@ -2266,7 +2272,7 @@ namespace FourSlash { runFourSlashTestContent(basePath, testType, content, fileName); } - export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void { + export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType | ((token: ts.HostCancellationToken) => Harness.LanguageService.LanguageServiceAdapter), content: string, fileName: string): void { // Parse out the files and their metadata const testData = parseTestData(basePath, content, fileName); diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f27e7e1c1749a..4579dbe4aae59 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1209,6 +1209,11 @@ namespace Harness { return normalized; } + function getDiagnosticCodeString(code: string | number) { + if (typeof code === "number") return `TS${code}`; + return code; + } + export function minimalDiagnosticsToString(diagnostics: ts.Diagnostic[]) { return ts.formatDiagnostics(diagnostics, { getCanonicalFileName, getCurrentDirectory: () => "", getNewLine: () => Harness.IO.newLine() }); } @@ -1226,7 +1231,7 @@ namespace Harness { .split("\n") .map(s => s.length > 0 && s.charAt(s.length - 1) === "\r" ? s.substr(0, s.length - 1) : s) .filter(s => s.length > 0) - .map(s => "!!! " + ts.DiagnosticCategory[error.category].toLowerCase() + " TS" + error.code + ": " + s); + .map(s => "!!! " + ts.DiagnosticCategory[error.category].toLowerCase() + " " + getDiagnosticCodeString(error.code) + ": " + s); errLines.forEach(e => outputLines.push(e)); // do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d7ed04b627f4f..fdd2f6602ecb5 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript: ts.Map = {}; + public fileNameToScript: ts.Map = {}; constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -366,6 +366,9 @@ namespace Harness.LanguageService { getCompilerOptionsDiagnostics(): ts.Diagnostic[] { return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); } + getProgramDiagnostics(): ts.Diagnostic[] { + return unwrapJSONCallResult(this.shim.getProgramDiagnostics()); + } getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); } diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 4b1945f5baa33..942ee516e9476 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -17,6 +17,7 @@ /// /// /// +/// /// /// @@ -58,6 +59,8 @@ function createRunner(kind: TestRunnerKind): RunnerBase { return new RWCRunner(); case "test262": return new Test262BaselineRunner(); + case "extension": + return new ExtensionRunner(); } } @@ -155,6 +158,9 @@ if (testConfigContent !== "") { case "test262": runners.push(new Test262BaselineRunner()); break; + case "extension": + runners.push(new ExtensionRunner()); + break; } } } @@ -176,6 +182,9 @@ if (runners.length === 0) { runners.push(new FourSlashRunner(FourSlashTestType.ShimsWithPreprocess)); runners.push(new FourSlashRunner(FourSlashTestType.Server)); // runners.push(new GeneratedFourslashRunner()); + + // extension + runners.push(new ExtensionRunner()); } if (taskConfigsFolder) { diff --git a/src/harness/runnerbase.ts b/src/harness/runnerbase.ts index 346382b7a5721..35d345463fde7 100644 --- a/src/harness/runnerbase.ts +++ b/src/harness/runnerbase.ts @@ -1,7 +1,7 @@ /// -type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262"; +type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "extension"; type CompilerTestKind = "conformance" | "compiler"; type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server"; diff --git a/src/server/client.ts b/src/server/client.ts index f04dbd8dc0253..d12c50459b6fe 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -395,6 +395,10 @@ namespace ts.server { } getCompilerOptionsDiagnostics(): Diagnostic[] { + return this.getProgramDiagnostics(); + } + + getProgramDiagnostics(): Diagnostic[] { throw new Error("Not Implemented Yet."); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6e9adad7f472f..acc12116ecb57 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1150,7 +1150,7 @@ namespace ts.server { info.setFormatOptions(this.getFormatCodeOptions()); this.filenameToScriptInfo[fileName] = info; if (!info.isOpen) { - info.fileWatcher = this.host.watchFile(fileName, _ => { this.watchedFileChanged(fileName); }); + info.fileWatcher = this.host.watchFile && this.host.watchFile(fileName, _ => { this.watchedFileChanged(fileName); }); } } } diff --git a/src/services/services.ts b/src/services/services.ts index ab1e30a44a607..7805fd19e03b9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1167,6 +1167,8 @@ namespace ts { resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; directoryExists?(directoryName: string): boolean; getDirectories?(directoryName: string): string[]; + + loadExtension?(path: string): any; } // @@ -1179,10 +1181,13 @@ namespace ts { getSyntacticDiagnostics(fileName: string): Diagnostic[]; getSemanticDiagnostics(fileName: string): Diagnostic[]; - // TODO: Rename this to getProgramDiagnostics to better indicate that these are any - // diagnostics present for the program level, and not just 'options' diagnostics. + /** + * @deprecated Use getProgramDiagnostics instead. + */ getCompilerOptionsDiagnostics(): Diagnostic[]; + getProgramDiagnostics(): Diagnostic[]; + /** * @deprecated Use getEncodedSyntacticClassifications instead. */ @@ -1826,6 +1831,7 @@ namespace ts { version: string; scriptSnapshot: IScriptSnapshot; scriptKind: ScriptKind; + isRoot: boolean; } interface DocumentRegistryEntry { @@ -1902,7 +1908,7 @@ namespace ts { // Initialize the list with the root file names const rootFileNames = host.getScriptFileNames(); for (const fileName of rootFileNames) { - this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName)); + this.createEntry(fileName, toPath(fileName, this.currentDirectory, getCanonicalFileName), /*isRoot*/true); } // store the compilation settings @@ -1913,7 +1919,7 @@ namespace ts { return this._compilationSettings; } - private createEntry(fileName: string, path: Path) { + private createEntry(fileName: string, path: Path, isRoot: boolean) { let entry: HostFileInformation; const scriptSnapshot = this.host.getScriptSnapshot(fileName); if (scriptSnapshot) { @@ -1921,7 +1927,8 @@ namespace ts { hostFileName: fileName, version: this.host.getScriptVersion(fileName), scriptSnapshot: scriptSnapshot, - scriptKind: getScriptKind(fileName, this.host) + scriptKind: getScriptKind(fileName, this.host), + isRoot }; } @@ -1945,14 +1952,14 @@ namespace ts { public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation { return this.contains(path) ? this.getEntry(path) - : this.createEntry(fileName, path); + : this.createEntry(fileName, path, /*isRoot*/false); } public getRootFileNames(): string[] { const fileNames: string[] = []; this.fileNameToEntry.forEachValue((path, value) => { - if (value) { + if (value && value.isRoot) { fileNames.push(value.hostFileName); } }); @@ -3024,6 +3031,7 @@ namespace ts { const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); let ruleProvider: formatting.RulesProvider; let program: Program; + let extensionCache: ExtensionCache; let lastProjectVersion: string; const useCaseSensitivefileNames = false; @@ -3114,11 +3122,13 @@ namespace ts { getCurrentDirectory: () => currentDirectory, fileExists: (fileName): boolean => { // stub missing host functionality + Debug.assert(!!hostCache, "LS CompilerHost may not persist beyond the execution of a synchronize call"); Debug.assert(!host.resolveModuleNames || !host.resolveTypeReferenceDirectives); return hostCache.getOrCreateEntry(fileName) !== undefined; }, readFile: (fileName): string => { // stub missing host functionality + Debug.assert(!!hostCache, "LS CompilerHost may not persist beyond the execution of a synchronize call"); const entry = hostCache.getOrCreateEntry(fileName); return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); }, @@ -3127,6 +3137,9 @@ namespace ts { }, getDirectories: path => { return host.getDirectories ? host.getDirectories(path) : []; + }, + loadExtension: path => { + return host.loadExtension ? host.loadExtension(path) : undefined; } }; if (host.trace) { @@ -3142,8 +3155,13 @@ namespace ts { }; } + const changesInCompilationSettingsAffectExtensions = oldSettings && !deepEqual(oldSettings.extensions, newSettings.extensions); + if (!extensionCache || changesInCompilationSettingsAffectExtensions) { + extensionCache = createExtensionCache(newSettings, compilerHost); + } + const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); - const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); + const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program, extensionCache); // Release any files we have acquired in the old program but are // not part of the new program. @@ -3304,7 +3322,7 @@ namespace ts { return concatenate(semanticDiagnostics, declarationDiagnostics); } - function getCompilerOptionsDiagnostics() { + function getProgramDiagnostics() { synchronizeHostData(); return program.getOptionsDiagnostics(cancellationToken).concat( program.getGlobalDiagnostics(cancellationToken)); @@ -8265,7 +8283,8 @@ namespace ts { cleanupSemanticCache, getSyntacticDiagnostics, getSemanticDiagnostics, - getCompilerOptionsDiagnostics, + getCompilerOptionsDiagnostics: getProgramDiagnostics, + getProgramDiagnostics, getSyntacticClassifications, getSemanticClassifications, getEncodedSyntacticClassifications, diff --git a/src/services/shims.ts b/src/services/shims.ts index 45c4b284ae744..a73133940bd6e 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -129,6 +129,7 @@ namespace ts { getSyntacticDiagnostics(fileName: string): string; getSemanticDiagnostics(fileName: string): string; getCompilerOptionsDiagnostics(): string; + getProgramDiagnostics(): string; getSyntacticClassifications(fileName: string, start: number, length: number): string; getSemanticClassifications(fileName: string, start: number, length: number): string; @@ -562,11 +563,11 @@ namespace ts { } } - export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number; }[] { + export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number | string; }[] { return diagnostics.map(d => realizeDiagnostic(d, newLine)); } - function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number; } { + function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number | string; } { return { message: flattenDiagnosticMessageText(diagnostic.messageText, newLine), start: diagnostic.start, @@ -699,6 +700,15 @@ namespace ts { }); } + public getProgramDiagnostics(): string { + return this.forwardJSONCall( + "getProgramDiagnostics()", + () => { + const diagnostics = this.languageService.getProgramDiagnostics(); + return this.realizeDiagnostics(diagnostics); + }); + } + /// QUICKINFO /** diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index cfeb7c2fcd582..7d8d5893212bf 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -25,6 +25,7 @@ "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", "../compiler/program.ts", + "../compiler/extensions.ts", "../compiler/commandLineParser.ts", "../compiler/diagnosticInformationMap.generated.ts", "breakpoints.ts", diff --git a/tests/baselines/reference/CompilerHost/reportsFailedLoads/test.errors.txt b/tests/baselines/reference/CompilerHost/reportsFailedLoads/test.errors.txt new file mode 100644 index 0000000000000..7d6ee017a2a6b --- /dev/null +++ b/tests/baselines/reference/CompilerHost/reportsFailedLoads/test.errors.txt @@ -0,0 +1,8 @@ +error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-semantic-lint'.'. +error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-syntactic-lint'.'. + + +!!! error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-semantic-lint'.'. +!!! error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-syntactic-lint'.'. +==== /hello.ts (0 errors) ==== + console.log("Hello, world!");/*EOL*/ \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/reportsFailedLoads/test.js b/tests/baselines/reference/CompilerHost/reportsFailedLoads/test.js new file mode 100644 index 0000000000000..21b4721f1a6ed --- /dev/null +++ b/tests/baselines/reference/CompilerHost/reportsFailedLoads/test.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.errors.txt b/tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.errors.txt new file mode 100644 index 0000000000000..7d6ee017a2a6b --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.errors.txt @@ -0,0 +1,8 @@ +error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-semantic-lint'.'. +error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-syntactic-lint'.'. + + +!!! error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-semantic-lint'.'. +!!! error TS6151: Extension loading failed with error 'Error: Host could not locate extension 'test-syntactic-lint'.'. +==== /hello.ts (0 errors) ==== + console.log("Hello, world!");/*EOL*/ \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.js b/tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.js new file mode 100644 index 0000000000000..b9f140e37bdde --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/reportsFailedLoads/test.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/library-reference-12.trace.json b/tests/baselines/reference/library-reference-12.trace.json index 84144f82729c6..25fb7f618cd70 100644 --- a/tests/baselines/reference/library-reference-12.trace.json +++ b/tests/baselines/reference/library-reference-12.trace.json @@ -17,6 +17,7 @@ "File '/a/node_modules/jquery.ts' does not exist.", "File '/a/node_modules/jquery.d.ts' does not exist.", "Found 'package.json' at '/a/node_modules/jquery/package.json'.", + "Expected type of 'typings' field in 'package.json' to be 'string', got 'undefined'.", "'package.json' has 'types' field 'dist/jquery.d.ts' that references '/a/node_modules/jquery/dist/jquery.d.ts'.", "File '/a/node_modules/jquery/dist/jquery.d.ts' exist - use it as a name resolution result.", "======== Type reference directive 'jquery' was successfully resolved to '/a/node_modules/jquery/dist/jquery.d.ts', primary: false. ========" diff --git a/tests/baselines/reference/library-reference-2.trace.json b/tests/baselines/reference/library-reference-2.trace.json index 64cdd8091832f..f8119ea65dbb9 100644 --- a/tests/baselines/reference/library-reference-2.trace.json +++ b/tests/baselines/reference/library-reference-2.trace.json @@ -2,12 +2,14 @@ "======== Resolving type reference directive 'jquery', containing file '/consumer.ts', root directory '/types'. ========", "Resolving with primary search path '/types'", "Found 'package.json' at '/types/jquery/package.json'.", + "Expected type of 'typings' field in 'package.json' to be 'string', got 'undefined'.", "'package.json' has 'types' field 'jquery.d.ts' that references '/types/jquery/jquery.d.ts'.", "File '/types/jquery/jquery.d.ts' exist - use it as a name resolution result.", "======== Type reference directive 'jquery' was successfully resolved to '/types/jquery/jquery.d.ts', primary: true. ========", "======== Resolving type reference directive 'jquery', containing file 'test/__inferred type names__.ts', root directory '/types'. ========", "Resolving with primary search path '/types'", "Found 'package.json' at '/types/jquery/package.json'.", + "Expected type of 'typings' field in 'package.json' to be 'string', got 'undefined'.", "'package.json' has 'types' field 'jquery.d.ts' that references '/types/jquery/jquery.d.ts'.", "File '/types/jquery/jquery.d.ts' exist - use it as a name resolution result.", "======== Type reference directive 'jquery' was successfully resolved to '/types/jquery/jquery.d.ts', primary: true. ========" diff --git a/tests/cases/extensions/available/extension-api/index.ts b/tests/cases/extensions/available/extension-api/index.ts new file mode 100644 index 0000000000000..086fc445a1c38 --- /dev/null +++ b/tests/cases/extensions/available/extension-api/index.ts @@ -0,0 +1,2 @@ +import * as tsi from "typescript"; +// No APIs exposed \ No newline at end of file diff --git a/tests/cases/extensions/available/extension-api/package.json b/tests/cases/extensions/available/extension-api/package.json new file mode 100644 index 0000000000000..cbd379499d2ec --- /dev/null +++ b/tests/cases/extensions/available/extension-api/package.json @@ -0,0 +1,8 @@ +{ + "name": "extension-api", + "version": "1.0.0", + "description": "", + "main": "index.js", + "author": "", + "types": "index.d.ts" +} \ No newline at end of file diff --git a/tests/cases/extensions/available/tsconfig.json b/tests/cases/extensions/available/tsconfig.json new file mode 100644 index 0000000000000..d36035609aafc --- /dev/null +++ b/tests/cases/extensions/available/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + // This baseUrl option is useful while writing tests - it lets us + // pretend all these modules can see one another (as if they were in a node_modules folder) + // since when they're loaded into the virtual fs the test host provides, they _will_ be in a + // node_modules folder + "baseUrl": "./" + } +} \ No newline at end of file diff --git a/tests/cases/extensions/available/typescript/package.json b/tests/cases/extensions/available/typescript/package.json new file mode 100644 index 0000000000000..14adb10419ff1 --- /dev/null +++ b/tests/cases/extensions/available/typescript/package.json @@ -0,0 +1,3 @@ +{ + "types": "../../../../built/local/typescript.d.ts" +} \ No newline at end of file diff --git a/tests/cases/extensions/scenarios/reportsFailedLoads/test.json b/tests/cases/extensions/scenarios/reportsFailedLoads/test.json new file mode 100644 index 0000000000000..221b4e3a867c0 --- /dev/null +++ b/tests/cases/extensions/scenarios/reportsFailedLoads/test.json @@ -0,0 +1,9 @@ +{ + "inputFiles": [ + "hello.ts" + ], + "availableExtensions": [], + "compilerOptions": { + "extensions": ["test-syntactic-lint", "test-semantic-lint"] + } +} \ No newline at end of file diff --git a/tests/cases/extensions/source/hello.ts b/tests/cases/extensions/source/hello.ts new file mode 100644 index 0000000000000..97d87624465a4 --- /dev/null +++ b/tests/cases/extensions/source/hello.ts @@ -0,0 +1 @@ +console.log("Hello, world!");/*EOL*/ \ No newline at end of file From 191db01cfbda8109cde9dece67dfe4ba0f23d142 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 4 Aug 2016 18:20:24 -0700 Subject: [PATCH 2/3] Lint extensions --- src/compiler/checker.ts | 4 +- src/compiler/extensions.ts | 144 +++++++++- src/compiler/program.ts | 257 +++++++++++++++++- src/compiler/utilities.ts | 18 ++ .../both-diagnostics.errors.txt | 18 ++ .../multiLintLoads/both-diagnostics.js | 7 + .../multiLintLoads/both-diagnostics.trace.txt | 18 ++ .../multiLintLoads/no-diagnostics.js | 5 + .../multiLintLoads/no-diagnostics.trace.txt | 18 ++ .../semantic-diagnostics.errors.txt | 13 + .../multiLintLoads/semantic-diagnostics.js | 5 + .../semantic-diagnostics.trace.txt | 18 ++ .../syntactic-diagnostics.errors.txt | 7 + .../multiLintLoads/syntactic-diagnostics.js | 4 + .../syntactic-diagnostics.trace.txt | 18 ++ .../test.errors.txt | 31 +++ .../test.js | 9 + .../passArguments/array.errors.txt | 10 + .../CompilerHost/passArguments/array.js | 4 + .../test.errors.txt | 114 ++++++++ .../reportsAllDIagnosticsFormats/test.js | 5 + .../test.errors.txt | 21 ++ .../reportsDiagnosticsWithShortnames/test.js | 9 + .../semanticLintLoads/no-diagnostics.js | 5 + .../no-diagnostics.trace.txt | 9 + .../w-diagnostics.errors.txt | 13 + .../semanticLintLoads/w-diagnostics.js | 5 + .../semanticLintLoads/w-diagnostics.trace.txt | 9 + .../syntacticLintLoads/no-diagnostics.js | 5 + .../no-diagnostics.trace.txt | 9 + .../w-diagnostics.errors.txt | 7 + .../syntacticLintLoads/w-diagnostics.js | 4 + .../w-diagnostics.trace.txt | 9 + .../both-diagnostics.errors.txt | 18 ++ .../multiLintLoads/both-diagnostics.js | 7 + .../multiLintLoads/both-diagnostics.trace.txt | 18 ++ .../multiLintLoads/no-diagnostics.js | 5 + .../multiLintLoads/no-diagnostics.trace.txt | 18 ++ .../semantic-diagnostics.errors.txt | 13 + .../multiLintLoads/semantic-diagnostics.js | 5 + .../semantic-diagnostics.trace.txt | 18 ++ .../syntactic-diagnostics.errors.txt | 7 + .../multiLintLoads/syntactic-diagnostics.js | 4 + .../syntactic-diagnostics.trace.txt | 18 ++ .../test.errors.txt | 31 +++ .../test.js | 9 + .../passArguments/array.errors.txt | 10 + .../passArguments/array.js | 4 + .../test.errors.txt | 114 ++++++++ .../reportsAllDIagnosticsFormats/test.js | 5 + .../test.errors.txt | 21 ++ .../reportsDiagnosticsWithShortnames/test.js | 9 + .../semanticLintLoads/no-diagnostics.js | 5 + .../no-diagnostics.trace.txt | 9 + .../w-diagnostics.errors.txt | 13 + .../semanticLintLoads/w-diagnostics.js | 5 + .../semanticLintLoads/w-diagnostics.trace.txt | 9 + .../syntacticLintLoads/no-diagnostics.js | 5 + .../no-diagnostics.trace.txt | 9 + .../w-diagnostics.errors.txt | 7 + .../syntacticLintLoads/w-diagnostics.js | 4 + .../w-diagnostics.trace.txt | 9 + .../available/extension-api/index.ts | 34 ++- .../extensions/available/test-errors/index.ts | 117 ++++++++ .../available/test-errors/package.json | 7 + .../test-extension-arguments/index.ts | 14 + .../test-extension-arguments/package.json | 7 + .../available/test-multi-extension/index.ts | 48 ++++ .../test-multi-extension/package.json | 7 + .../available/test-multierrors/index.ts | 29 ++ .../available/test-multierrors/package.json | 7 + .../available/test-semantic-lint/main.ts | 14 + .../available/test-semantic-lint/package.json | 7 + .../available/test-syntactic-lint/index.ts | 12 + .../test-syntactic-lint/package.json | 7 + .../multiLintLoads/both-diagnostics.json | 10 + .../multiLintLoads/no-diagnostics.json | 10 + .../multiLintLoads/semantic-diagnostics.json | 10 + .../multiLintLoads/syntactic-diagnostics.json | 10 + .../test.json | 9 + .../scenarios/passArguments/array.json | 11 + .../reportsAllDIagnosticsFormats/test.json | 9 + .../test.json | 9 + .../semanticLintLoads/no-diagnostics.json | 10 + .../semanticLintLoads/w-diagnostics.json | 10 + .../syntacticLintLoads/no-diagnostics.json | 10 + .../syntacticLintLoads/w-diagnostics.json | 10 + .../source/foo-bar-const-interface.ts | 4 + tests/cases/extensions/source/foo-bar-math.ts | 3 + tests/cases/extensions/source/foo-const.ts | 1 + .../extensions/source/foo-interface-const.ts | 2 + .../cases/extensions/source/foo-interface.ts | 1 + 92 files changed, 1674 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.js create mode 100644 tests/baselines/reference/CompilerHost/passArguments/array.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/passArguments/array.js create mode 100644 tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.js create mode 100644 tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.js create mode 100644 tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.trace.txt create mode 100644 tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.errors.txt create mode 100644 tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.js create mode 100644 tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.js create mode 100644 tests/baselines/reference/LanguageServiceHost/passArguments/array.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/passArguments/array.js create mode 100644 tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.js create mode 100644 tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.js create mode 100644 tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.trace.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.errors.txt create mode 100644 tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.js create mode 100644 tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.trace.txt create mode 100644 tests/cases/extensions/available/test-errors/index.ts create mode 100644 tests/cases/extensions/available/test-errors/package.json create mode 100644 tests/cases/extensions/available/test-extension-arguments/index.ts create mode 100644 tests/cases/extensions/available/test-extension-arguments/package.json create mode 100644 tests/cases/extensions/available/test-multi-extension/index.ts create mode 100644 tests/cases/extensions/available/test-multi-extension/package.json create mode 100644 tests/cases/extensions/available/test-multierrors/index.ts create mode 100644 tests/cases/extensions/available/test-multierrors/package.json create mode 100644 tests/cases/extensions/available/test-semantic-lint/main.ts create mode 100644 tests/cases/extensions/available/test-semantic-lint/package.json create mode 100644 tests/cases/extensions/available/test-syntactic-lint/index.ts create mode 100644 tests/cases/extensions/available/test-syntactic-lint/package.json create mode 100644 tests/cases/extensions/scenarios/multiLintLoads/both-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/multiLintLoads/no-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/multiLintLoads/semantic-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/multiLintLoads/syntactic-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/multipleRulesFoundInSingleExtension/test.json create mode 100644 tests/cases/extensions/scenarios/passArguments/array.json create mode 100644 tests/cases/extensions/scenarios/reportsAllDIagnosticsFormats/test.json create mode 100644 tests/cases/extensions/scenarios/reportsDiagnosticsWithShortnames/test.json create mode 100644 tests/cases/extensions/scenarios/semanticLintLoads/no-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/semanticLintLoads/w-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/syntacticLintLoads/no-diagnostics.json create mode 100644 tests/cases/extensions/scenarios/syntacticLintLoads/w-diagnostics.json create mode 100644 tests/cases/extensions/source/foo-bar-const-interface.ts create mode 100644 tests/cases/extensions/source/foo-bar-math.ts create mode 100644 tests/cases/extensions/source/foo-const.ts create mode 100644 tests/cases/extensions/source/foo-interface-const.ts create mode 100644 tests/cases/extensions/source/foo-interface.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0599eb4bbbc60..13eaf27a738b9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17948,9 +17948,9 @@ namespace ts { return getTypeOfSymbol(symbol); } - if (isDeclarationName(node)) { + if (isDeclarationName(node) || node.kind === SyntaxKind.SourceFile) { const symbol = getSymbolAtLocation(node); - return symbol && getTypeOfSymbol(symbol); + return symbol && getTypeOfSymbol(symbol) || unknownType; } if (isBindingPattern(node)) { diff --git a/src/compiler/extensions.ts b/src/compiler/extensions.ts index 071d3e7f3ec3d..3d887dd6800ac 100644 --- a/src/compiler/extensions.ts +++ b/src/compiler/extensions.ts @@ -1,10 +1,125 @@ namespace ts { + export type LintErrorMethod = { + /** + * @param {string} err The error message to report + */ + (err: string): void; + /** + * @param {string} err The error message to report + * @param {Node} span The node on which to position the error + */ + (err: string, span: Node): void; + /** + * @param {string} err The error message to report + * @param {number} start The start position of the error span + * @param {number} length The length of the error span + */ + (err: string, start: number, length: number): void; + /** + * @param {string} shortname A short code uniquely identifying the error within the lint + * @param {string} err The error message to report + */ + (shortname: string, err: string): void; + /** + * @param {string} shortname A short code uniquely identifying the error within the lint + * @param {string} err The error message to report + * @param {Node} span The node on which to position the error + */ + (shortname: string, err: string, span: Node): void; + /** + * @param {string} shortname A short code uniquely identifying the error within the lint + * @param {string} err The error message to report + * @param {number} start The start position of the error span + * @param {number} length The length of the error span + */ + (shortname: string, err: string, start: number, length: number): void; + /** + * @param {DiagnosticCategory} level The error level to report this error as (Message, Warning, or Error) + * @param {string} err The error message to report + */ + (level: DiagnosticCategory, err: string): void; + /** + * @param {DiagnosticCategory} level The error level to report this error as (Message, Warning, or Error) + * @param {string} err The error message to report + * @param {Node} span The node on which to position the error + */ + (level: DiagnosticCategory, err: string, span: Node): void; + /** + * @param {DiagnosticCategory} level The error level to report this error as (Message, Warning, or Error) + * @param {string} err The error message to report + * @param {number} start The start position of the error span + * @param {number} length The length of the error span + */ + (level: DiagnosticCategory, err: string, start: number, length: number): void; + /** + * @param {DiagnosticCategory} level The error level to report this error as (Message, Warning, or Error) + * @param {string} shortname A short code uniquely identifying the error within the lint + * @param {string} err The error message to report + */ + (level: DiagnosticCategory, shortname: string, err: string): void; + /** + * @param {DiagnosticCategory} level The error level to report this error as (Message, Warning, or Error) + * @param {string} shortname A short code uniquely identifying the error within the lint + * @param {string} err The error message to report + * @param {Node} span The node on which to position the error + */ + (level: DiagnosticCategory, shortname: string, err: string, span: Node): void; + /** + * @param {DiagnosticCategory} level The error level to report this error as (Message, Warning, or Error) + * @param {string} shortname A short code uniquely identifying the error within the lint + * @param {string} err The error message to report + * @param {number} start The start position of the error span + * @param {number} length The length of the error span + */ + (level: DiagnosticCategory, shortname: string, err: string, start: number, length: number): void; + }; + + /* + * Walkers call stop to halt recursion into the node's children + * Walkers call error to add errors to the output. + */ + export interface LintWalker { + /** + * Called as the walker enters the node. + * @param {Node} node The current node being visited (starts at every SourceFile and recurs into their children) + * @param {LintErrorMethod} error A callback to add errors to the output + * @returns boolean true if this lint no longer needs to recur into the active node + */ + visit(node: Node, error: LintErrorMethod): boolean | void; + /** + * Called as the visitor walks out of a node and back to its parent. + * @param {Node} node The current node which has just finished being visited + * @param {LintErrorMethod} error A callback to add errors to the output + */ + afterVisit?(node: Node, error: LintErrorMethod): void; + } + + export interface BaseProviderStatic { + readonly ["extension-kind"]: ExtensionKind; + new (state: {ts: typeof ts, args: any}): any; + } + + export interface SyntacticLintProviderStatic extends BaseProviderStatic { + readonly ["extension-kind"]: ExtensionKind.SyntacticLint; + new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program}): LintWalker; + } + + export interface SemanticLintProviderStatic extends BaseProviderStatic { + readonly ["extension-kind"]: ExtensionKind.SemanticLint; + new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program, checker: TypeChecker}): LintWalker; + } export namespace ExtensionKind { + export const SemanticLint: "semantic-lint" = "semantic-lint"; + export type SemanticLint = "semantic-lint"; + export const SyntacticLint: "syntactic-lint" = "syntactic-lint"; + export type SyntacticLint = "syntactic-lint"; } - export type ExtensionKind = string; + export type ExtensionKind = ExtensionKind.SemanticLint | ExtensionKind.SyntacticLint; export interface ExtensionCollectionMap { + "semantic-lint"?: SemanticLintExtension[]; + "syntactic-lint"?: SyntacticLintExtension[]; [index: string]: Extension[] | undefined; } @@ -21,7 +136,17 @@ namespace ts { length?: number; } - export type Extension = ExtensionBase; + export interface SyntacticLintExtension extends ExtensionBase { + kind: ExtensionKind.SyntacticLint; + ctor: SyntacticLintProviderStatic; + } + + export interface SemanticLintExtension extends ExtensionBase { + kind: ExtensionKind.SemanticLint; + ctor: SemanticLintProviderStatic; + } + + export type Extension = SyntacticLintExtension | SemanticLintExtension; export interface ExtensionCache { getCompilerExtensions(): ExtensionCollectionMap; @@ -173,6 +298,21 @@ namespace ts { kind: annotatedKind as ExtensionKind, }; switch (ext.kind) { + case ExtensionKind.SemanticLint: + case ExtensionKind.SyntacticLint: + if (typeof potentialExtension !== "function") { + diagnostics.push(createCompilerDiagnostic( + Diagnostics.Extension_0_exported_member_1_has_extension_kind_2_but_was_type_3_when_type_4_was_expected, + res.name, + key, + (ts as any).ExtensionKind[annotatedKind], + typeof potentialExtension, + "function" + )); + return; + } + (ext as (SemanticLintExtension | SyntacticLintExtension)).ctor = potentialExtension as (SemanticLintProviderStatic | SyntacticLintProviderStatic); + break; default: // Include a default case which just puts the extension unchecked onto the base extension // This can allow language service extensions to query for custom extension kinds diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9ba91f41bac81..bcf7b4efb8f36 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1517,7 +1517,261 @@ namespace ts { } } + function performLintPassOnFile(sourceFile: SourceFile, kind: ExtensionKind.SyntacticLint | ExtensionKind.SemanticLint): Diagnostic[] | undefined { + const lints = extensionCache.getCompilerExtensions()[kind]; + if (!lints || !lints.length) { + return; + } + type UniqueLint = {name: string, walker: LintWalker, accepted: boolean, errored: boolean}; + const initializedLints: UniqueLint[] = []; + const diagnostics: Diagnostic[] = []; + let activeLint: UniqueLint; + let parent: Node | undefined = undefined; + const profilingEnabled = options.extendedDiagnostics; + for (const {name, args, ctor} of lints) { + let walker: LintWalker; + if (kind === ExtensionKind.SemanticLint) { + const checker = getTypeChecker(); + startExtensionProfile(profilingEnabled, name, "construct"); + try { + walker = new (ctor as SemanticLintProviderStatic)({ ts, checker, args, host, program }); + } + catch (e) { + diagnostics.push(createExtensionDiagnostic(name, `Lint construction failed: ${(e as Error).message}`, sourceFile, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); + } + completeExtensionProfile(profilingEnabled, name, "construct"); + } + else if (kind === ExtensionKind.SyntacticLint) { + startExtensionProfile(profilingEnabled, name, "construct"); + try { + walker = new (ctor as SyntacticLintProviderStatic)({ ts, args, host, program }); + } + catch (e) { + diagnostics.push(createExtensionDiagnostic(name, `Lint construction failed: ${(e as Error).message}`, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); + } + completeExtensionProfile(profilingEnabled, name, "construct"); + } + if (walker) initializedLints.push({ name, walker, accepted: true, errored: false }); + } + + function visitNode(node: Node) { + let oneAccepted = false; + const needsReset: Map = {}; + + // Ensure parent pointer is set + node.parent = parent; + + // For each lint, except those which have errored or have not accepted a parental node, call its rule + for (let i = 0; i < initializedLints.length; i++) { + activeLint = initializedLints[i]; + if (activeLint.errored || !activeLint.accepted) { + continue; + } + + startExtensionProfile(profilingEnabled, activeLint.name, "visit"); + try { + activeLint.accepted = !activeLint.walker.visit(node, error); + } + catch (e) { + activeLint.errored = true; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName("!!!"), `visit failed with error: ${e}`, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); + } + completeExtensionProfile(profilingEnabled, activeLint.name, "visit"); + + if (activeLint.accepted) { + oneAccepted = true; + } + else { + needsReset[i] = true; + } + } + + // Save the parent, then recur into the child nodes + const oldParent = parent; + parent = node; + if (oneAccepted) { + forEachChild(node, visitNode); + } + parent = oldParent; + + // All this node's children have been processed, for each lint + // - If it set accepted to false during this node (needsReset), set it back to true + // - If it has an afterVisit method, call it + for (let i = 0; i < initializedLints.length; i++) { + activeLint = initializedLints[i]; + if (activeLint.errored) { + continue; + } + if (!needsReset[i]) { + activeLint.accepted = true; + needsReset[i] = false; + } + if (!activeLint.walker.afterVisit) { + continue; + } + startExtensionProfile(profilingEnabled, activeLint.name, "afterVisit"); + try { + activeLint.walker.afterVisit(node, error); + } + catch (e) { + activeLint.errored = true; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName("!!!"), `afterVisit failed with error: ${e}`, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); + } + completeExtensionProfile(profilingEnabled, activeLint.name, "afterVisit"); + } + } + + function errorQualifiedName(shortname?: string) { + if (!shortname) return activeLint.name; + return `${activeLint.name}(${shortname})`; + } + + function ensure(param: any, paramName: string, istype: string) { + if (typeof param !== istype) { + Debug.fail(`${paramName} must be a ${istype}.`); + } + } + + const errorOverloads: ((...args: any[]) => void)[] = [() => { + Debug.fail("You must at least provide an error message to the error function."); + }, + (err: string) => { + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(), err)); + }, + (errShortnameOrLevel: string | DiagnosticCategory, spanOrErr: Node | string | number) => { + if (typeof errShortnameOrLevel === "string") { + if (typeof spanOrErr === "string") { + // error(shortname: string, err: string): void; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(errShortnameOrLevel), spanOrErr)); + } + else if (typeof spanOrErr === "object") { + // error(err: string, node: Node): void; + diagnostics.push(createExtensionDiagnosticForNode(spanOrErr, errorQualifiedName(), errShortnameOrLevel)); + } + else { + Debug.fail("When two arguments are passed to error and the first argument is either the error message or error shortname, the second argument must either be an error message string or the node the error is on."); + } + } + else if (typeof errShortnameOrLevel === "number") { + ensure(spanOrErr, "Error message", "string"); + + // error(level: DiagnosticCategory, err: string): void; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(), spanOrErr as string, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, errShortnameOrLevel)); + } + else { + Debug.fail("When two arguments are passed to error, the first argument must be either the error message, the error shortname, or the DiagnosticCategory of the error."); + } + }, + (errShortnameOrLevel: string | DiagnosticCategory, startErrOrShortname: number | string | Node, lengthSpanOrErr: number | Node | string) => { + if (typeof errShortnameOrLevel === "number") { + ensure(startErrOrShortname, "Error message or error shortcode", "string"); + + if (typeof lengthSpanOrErr === "string") { + // error(level: DiagnosticCategory, shortname: string, err: string): void; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(startErrOrShortname as string), lengthSpanOrErr, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, errShortnameOrLevel)); + } + else if (typeof lengthSpanOrErr === "object") { + // error(level: DiagnosticCategory, err: string, node: Node): void; + diagnostics.push(createExtensionDiagnosticForNode(lengthSpanOrErr, errorQualifiedName(), startErrOrShortname as string, errShortnameOrLevel)); + } + else { + Debug.fail("When three arguments are passed to error and the first is a diagnostic category, the third argument must either be an error message or the node errored on."); + } + } + else if (typeof errShortnameOrLevel === "string") { + if (typeof startErrOrShortname === "number") { + ensure(lengthSpanOrErr, "Error span length", "number"); + + // error(err: string, start: number, length: number): void; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(), errShortnameOrLevel, sourceFile, startErrOrShortname, lengthSpanOrErr as number)); + } + else if (typeof startErrOrShortname === "string") { + ensure(lengthSpanOrErr, "Error node", "object"); + + // error(shortname: string, err: string, span: Node): void; + diagnostics.push(createExtensionDiagnosticForNode(lengthSpanOrErr as Node, errorQualifiedName(errShortnameOrLevel), startErrOrShortname)); + } + else { + Debug.fail("When three arguments are passed to error and the first is an error shortcode or message, the second must be either an error message or a span start number, respectively."); + } + } + else { + Debug.fail("When three arguments are passed to error, the first argument must be either a diagnostic category, a error shortname, or an error message."); + } + }, + (levelOrShortname: DiagnosticCategory | string, errOrShortname: string | number | Node, startOrErr: string | number | Node, lengthOrSpan: number | Node) => { + ensure(errOrShortname, "Error message or error shortcode", "string"); + + if (typeof levelOrShortname === "number") { + if (typeof startOrErr === "string") { + ensure(lengthOrSpan, "Error node", "object"); + + // error(level: DiagnosticCategory, shortname: string, err: string, span: Node): void; + diagnostics.push(createExtensionDiagnosticForNode(lengthOrSpan as Node, errorQualifiedName(errOrShortname as string), startOrErr, levelOrShortname)); + } + else if (typeof startOrErr === "number") { + ensure(lengthOrSpan, "Error span length", "number"); + + // error(level: DiagnosticCategory, err: string, start: number, length: number): void; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(), errOrShortname as string, sourceFile, startOrErr, lengthOrSpan as number, levelOrShortname)); + } + else { + Debug.fail("When four arguments are passed to error and the first is a DiagnosticCategory, the third argument must be the error message."); + return; + } + } + else if (typeof levelOrShortname === "string") { + ensure(startOrErr, "Error span start", "number"); + ensure(lengthOrSpan, "Error span length", "number"); + + // error(shortname: string, err: string, start: number, length: number): void; + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(levelOrShortname), errOrShortname as string, sourceFile, startOrErr as number, lengthOrSpan as number)); + } + else { + Debug.fail("When four arguments are passed to error, the first argument must be either a DiagnosticCategory or a error shortname string."); + } + }, + (level: DiagnosticCategory, shortname: string, err: string, start: number, length: number) => { + ensure(level, "Diagnostic level", "number"); + ensure(shortname, "Error shortcode", "string"); + ensure(err, "Error message", "string"); + ensure(start, "Error span start", "number"); + ensure(length, "Error span length", "number"); + + diagnostics.push(createExtensionDiagnostic(errorQualifiedName(shortname), err, sourceFile, start, length, level)); + }]; + + function tooManyArguments(length: number, found: number) { + return () => Debug.fail(`Too many arguments provided to error (Expected up to ${length}, but found ${found}).`); + } + + function error(err: string): void; + function error(err: string, node: Node): void; + function error(err: string, start: number, length: number): void; + function error(shortname: string, err: string): void; + function error(shortname: string, err: string, span: Node): void; + function error(shortname: string, err: string, start: number, length: number): void; + function error(level: DiagnosticCategory, err: string): void; + function error(level: DiagnosticCategory, err: string, node: Node): void; + function error(level: DiagnosticCategory, err: string, start: number, length: number): void; + function error(level: DiagnosticCategory, shortname: string, err: string): void; + function error(level: DiagnosticCategory, shortname: string, err: string, span: Node): void; + function error(level: DiagnosticCategory, shortname: string, err: string, start: number, length: number): void; + function error(): void { + (errorOverloads[arguments.length] || tooManyArguments(errorOverloads.length - 1, arguments.length)).apply(/*thisArg*/undefined, arguments); + } + + visitNode(sourceFile); + return diagnostics; + } + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + if (!sourceFile.isDeclarationFile) { + const lintDiagnostics = performLintPassOnFile(sourceFile, ExtensionKind.SyntacticLint); + if (lintDiagnostics && lintDiagnostics.length) { + return sourceFile.parseDiagnostics.concat(lintDiagnostics); + } + } return sourceFile.parseDiagnostics; } @@ -1558,8 +1812,9 @@ namespace ts { typeChecker.getDiagnostics(sourceFile, cancellationToken); const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); + const lintDiagnostics = (!sourceFile.isDeclarationFile) ? (performLintPassOnFile(sourceFile, ExtensionKind.SemanticLint) || []) : []; - return bindDiagnostics.concat(checkDiagnostics).concat(fileProcessingDiagnosticsInFile).concat(programDiagnosticsInFile); + return bindDiagnostics.concat(checkDiagnostics).concat(fileProcessingDiagnosticsInFile).concat(programDiagnosticsInFile).concat(lintDiagnostics); }); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4ae0005e9c5e7..367b7697ec405 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -456,6 +456,24 @@ namespace ts { return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2); } + export function createExtensionDiagnosticForNode(node: Node, extension: string, message: string, category?: DiagnosticCategory): Diagnostic { + const sourceFile = getSourceFileOfNode(node); + const span = getErrorSpanForNode(sourceFile, node); + return createExtensionDiagnostic(extension, message, sourceFile, span.start, span.length, category); + } + + export function createExtensionDiagnostic(extension: string, message: string, file?: SourceFile, start?: number, length?: number, category?: DiagnosticCategory): Diagnostic { + Debug.assert(file ? typeof start === "number" && typeof length === "number" : true, "File, start, and length must all be specified or unspecified."); + return { + file: file, + messageText: message, + code: extension, + start: start, + length: length, + category: typeof category !== "undefined" ? category : DiagnosticCategory.Warning + }; + } + export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic { const sourceFile = getSourceFileOfNode(node); const span = getErrorSpanForNode(sourceFile, node); diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.errors.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.errors.txt new file mode 100644 index 0000000000000..192f7279e1a29 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.errors.txt @@ -0,0 +1,18 @@ +/foo-interface-const.ts(1,11): warning test-syntactic-lint: Identifier 'foo' is forbidden. +/foo-interface-const.ts(2,7): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-interface-const.ts(2,10): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-interface-const.ts(2,18): warning test-semantic-lint: String literal type 'foo' is forbidden. + + +==== /foo-interface-const.ts (4 errors) ==== + interface Foo {a; b;} + ~~~ +!!! warning test-syntactic-lint: Identifier 'foo' is forbidden. + const s: "foo" = "foo"; + ~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.js b/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.js new file mode 100644 index 0000000000000..f49637b43b8c4 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.js @@ -0,0 +1,7 @@ +//// [foo-interface-const.ts] +interface Foo {a; b;} +const s: "foo" = "foo"; + + +//// [foo-interface-const.js] +var s = "foo"; diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/both-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.js b/tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.js new file mode 100644 index 0000000000000..21b4721f1a6ed --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/no-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.errors.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.errors.txt new file mode 100644 index 0000000000000..7b4b32f665de6 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.errors.txt @@ -0,0 +1,13 @@ +/foo-const.ts(1,7): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,10): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,18): warning test-semantic-lint: String literal type 'foo' is forbidden. + + +==== /foo-const.ts (3 errors) ==== + const s: "foo" = "foo"; + ~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.js b/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.js new file mode 100644 index 0000000000000..93ea8cd509684 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.js @@ -0,0 +1,5 @@ +//// [foo-const.ts] +const s: "foo" = "foo"; + +//// [foo-const.js] +var s = "foo"; diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/semantic-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.errors.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.errors.txt new file mode 100644 index 0000000000000..26aff22d2534b --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.errors.txt @@ -0,0 +1,7 @@ +/foo-interface.ts(1,11): warning test-syntactic-lint: Identifier 'foo' is forbidden. + + +==== /foo-interface.ts (1 errors) ==== + interface Foo {a; b;} + ~~~ +!!! warning test-syntactic-lint: Identifier 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.js b/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.js new file mode 100644 index 0000000000000..9bb521dc08570 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.js @@ -0,0 +1,4 @@ +//// [foo-interface.ts] +interface Foo {a; b;} + +//// [foo-interface.js] diff --git a/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multiLintLoads/syntactic-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.errors.txt b/tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.errors.txt new file mode 100644 index 0000000000000..f2f78f412a2cb --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.errors.txt @@ -0,0 +1,31 @@ +/foo-bar-const-interface.ts(1,11): warning test-multi-extension[IsNamedFoo]: Identifier 'foo' is forbidden. +/foo-bar-const-interface.ts(2,11): warning test-multi-extension[IsNamedBar]: Identifier 'bar' is forbidden. +/foo-bar-const-interface.ts(3,7): warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. +/foo-bar-const-interface.ts(3,10): warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. +/foo-bar-const-interface.ts(3,18): warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. +/foo-bar-const-interface.ts(4,5): warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. +/foo-bar-const-interface.ts(4,8): warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. +/foo-bar-const-interface.ts(4,16): warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. + + +==== /foo-bar-const-interface.ts (8 errors) ==== + interface Foo {b;} + ~~~ +!!! warning test-multi-extension[IsNamedFoo]: Identifier 'foo' is forbidden. + interface Bar {a;} + ~~~ +!!! warning test-multi-extension[IsNamedBar]: Identifier 'bar' is forbidden. + const f: "foo" = "foo"; + ~ +!!! warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. + let b: "bar" = "bar"; + ~ +!!! warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.js b/tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.js new file mode 100644 index 0000000000000..b0cf15485cf35 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/multipleRulesFoundInSingleExtension/test.js @@ -0,0 +1,9 @@ +//// [foo-bar-const-interface.ts] +interface Foo {b;} +interface Bar {a;} +const f: "foo" = "foo"; +let b: "bar" = "bar"; + +//// [foo-bar-const-interface.js] +var f = "foo"; +var b = "bar"; diff --git a/tests/baselines/reference/CompilerHost/passArguments/array.errors.txt b/tests/baselines/reference/CompilerHost/passArguments/array.errors.txt new file mode 100644 index 0000000000000..111f82a3d9064 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/passArguments/array.errors.txt @@ -0,0 +1,10 @@ +/foo-interface.ts(1,16): warning test-extension-arguments: Identifier a is forbidden. +/foo-interface.ts(1,19): warning test-extension-arguments: Identifier b is forbidden. + + +==== /foo-interface.ts (2 errors) ==== + interface Foo {a; b;} + ~ +!!! warning test-extension-arguments: Identifier a is forbidden. + ~ +!!! warning test-extension-arguments: Identifier b is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/passArguments/array.js b/tests/baselines/reference/CompilerHost/passArguments/array.js new file mode 100644 index 0000000000000..9bb521dc08570 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/passArguments/array.js @@ -0,0 +1,4 @@ +//// [foo-interface.ts] +interface Foo {a; b;} + +//// [foo-interface.js] diff --git a/tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.errors.txt b/tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.errors.txt new file mode 100644 index 0000000000000..9e6fc2a47ab5c --- /dev/null +++ b/tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.errors.txt @@ -0,0 +1,114 @@ +warning test-errors: Not allowed. +warning test-errors[Throws2](THROWS2): Not allowed. +error test-errors[Throws7]: Not allowed. +error test-errors[Throws8](THROWS8): Not allowed. +error test-errors[ThrowsOnAfterVisit](!!!): afterVisit failed with error: Error: Throws on afterVisit +error test-errors[ThrowsOnConstruct]: Lint construction failed: Throws on construct +error test-errors[ThrowsOnVisit](!!!): visit failed with error: Error: Throws on visit +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,1): error test-errors[Throws10](THROWS10): Not allowed. +/hello.ts(1,1): error test-errors[Throws12]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws4](THROWS4): Not allowed. +/hello.ts(1,1): warning test-errors[Throws6]: Not allowed. +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,9): error test-errors[Throws11]: Not allowed. +/hello.ts(1,9): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,9): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,9): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,13): error test-errors[Throws11]: Not allowed. +/hello.ts(1,13): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,13): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,13): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,37): error test-errors[Throws11]: Not allowed. +/hello.ts(1,37): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,37): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,37): error test-errors[Throws9](THROWS9): Not allowed. + + +!!! warning test-errors: Not allowed. +!!! warning test-errors[Throws2](THROWS2): Not allowed. +!!! error test-errors[Throws7]: Not allowed. +!!! error test-errors[Throws8](THROWS8): Not allowed. +!!! error test-errors[ThrowsOnAfterVisit](!!!): afterVisit failed with error: Error: Throws on afterVisit +!!! error test-errors[ThrowsOnConstruct]: Lint construction failed: Throws on construct +!!! error test-errors[ThrowsOnVisit](!!!): visit failed with error: Error: Throws on visit +==== /hello.ts (32 errors) ==== + console.log("Hello, world!");/*EOL*/ + ~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~ +!!! error test-errors[Throws10](THROWS10): Not allowed. + ~~~~~~~~~~ +!!! error test-errors[Throws12]: Not allowed. + ~~~~~~~~~~ +!!! warning test-errors[Throws4](THROWS4): Not allowed. + ~~~~~~~~~~ +!!! warning test-errors[Throws6]: Not allowed. + ~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + +!!! error test-errors[Throws11]: Not allowed. + +!!! warning test-errors[Throws3](THROWS3): Not allowed. + +!!! warning test-errors[Throws5]: Not allowed. + +!!! error test-errors[Throws9](THROWS9): Not allowed. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.js b/tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.js new file mode 100644 index 0000000000000..21b4721f1a6ed --- /dev/null +++ b/tests/baselines/reference/CompilerHost/reportsAllDIagnosticsFormats/test.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.errors.txt b/tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.errors.txt new file mode 100644 index 0000000000000..be0e109414197 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.errors.txt @@ -0,0 +1,21 @@ +/foo-bar-math.ts(1,7): warning test-multierrors(FOO): Identifier 'foo' is forbidden. +/foo-bar-math.ts(1,7): warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. +/foo-bar-math.ts(2,7): warning test-multierrors(BAR): Identifier 'bar' is forbidden. +/foo-bar-math.ts(2,7): warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. +/foo-bar-math.ts(3,7): warning test-multierrors[NoShortNames](SINGLE): Single character identifiers are forbidden + + +==== /foo-bar-math.ts (5 errors) ==== + const foo = 3; + ~~~ +!!! warning test-multierrors(FOO): Identifier 'foo' is forbidden. + ~~~ +!!! warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. + const bar = 4; + ~~~ +!!! warning test-multierrors(BAR): Identifier 'bar' is forbidden. + ~~~ +!!! warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. + const x = 3 * 4; + ~ +!!! warning test-multierrors[NoShortNames](SINGLE): Single character identifiers are forbidden \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.js b/tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.js new file mode 100644 index 0000000000000..27d755f6ea35e --- /dev/null +++ b/tests/baselines/reference/CompilerHost/reportsDiagnosticsWithShortnames/test.js @@ -0,0 +1,9 @@ +//// [foo-bar-math.ts] +const foo = 3; +const bar = 4; +const x = 3 * 4; + +//// [foo-bar-math.js] +var foo = 3; +var bar = 4; +var x = 3 * 4; diff --git a/tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.js b/tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.js new file mode 100644 index 0000000000000..21b4721f1a6ed --- /dev/null +++ b/tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.trace.txt new file mode 100644 index 0000000000000..acf7782714e0c --- /dev/null +++ b/tests/baselines/reference/CompilerHost/semanticLintLoads/no-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.errors.txt b/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.errors.txt new file mode 100644 index 0000000000000..7b4b32f665de6 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.errors.txt @@ -0,0 +1,13 @@ +/foo-const.ts(1,7): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,10): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,18): warning test-semantic-lint: String literal type 'foo' is forbidden. + + +==== /foo-const.ts (3 errors) ==== + const s: "foo" = "foo"; + ~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.js b/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.js new file mode 100644 index 0000000000000..93ea8cd509684 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.js @@ -0,0 +1,5 @@ +//// [foo-const.ts] +const s: "foo" = "foo"; + +//// [foo-const.js] +var s = "foo"; diff --git a/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.trace.txt new file mode 100644 index 0000000000000..acf7782714e0c --- /dev/null +++ b/tests/baselines/reference/CompilerHost/semanticLintLoads/w-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.js b/tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.js new file mode 100644 index 0000000000000..21b4721f1a6ed --- /dev/null +++ b/tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.trace.txt new file mode 100644 index 0000000000000..76f57ec088c21 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/syntacticLintLoads/no-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.errors.txt b/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.errors.txt new file mode 100644 index 0000000000000..26aff22d2534b --- /dev/null +++ b/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.errors.txt @@ -0,0 +1,7 @@ +/foo-interface.ts(1,11): warning test-syntactic-lint: Identifier 'foo' is forbidden. + + +==== /foo-interface.ts (1 errors) ==== + interface Foo {a; b;} + ~~~ +!!! warning test-syntactic-lint: Identifier 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.js b/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.js new file mode 100644 index 0000000000000..9bb521dc08570 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.js @@ -0,0 +1,4 @@ +//// [foo-interface.ts] +interface Foo {a; b;} + +//// [foo-interface.js] diff --git a/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.trace.txt b/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.trace.txt new file mode 100644 index 0000000000000..76f57ec088c21 --- /dev/null +++ b/tests/baselines/reference/CompilerHost/syntacticLintLoads/w-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.errors.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.errors.txt new file mode 100644 index 0000000000000..192f7279e1a29 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.errors.txt @@ -0,0 +1,18 @@ +/foo-interface-const.ts(1,11): warning test-syntactic-lint: Identifier 'foo' is forbidden. +/foo-interface-const.ts(2,7): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-interface-const.ts(2,10): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-interface-const.ts(2,18): warning test-semantic-lint: String literal type 'foo' is forbidden. + + +==== /foo-interface-const.ts (4 errors) ==== + interface Foo {a; b;} + ~~~ +!!! warning test-syntactic-lint: Identifier 'foo' is forbidden. + const s: "foo" = "foo"; + ~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.js new file mode 100644 index 0000000000000..7cace2fc303ed --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.js @@ -0,0 +1,7 @@ +//// [foo-interface-const.ts] +interface Foo {a; b;} +const s: "foo" = "foo"; + + +//// [foo-interface-const.js] +var s = "foo"; diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/both-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.js new file mode 100644 index 0000000000000..b9f140e37bdde --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/no-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.errors.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.errors.txt new file mode 100644 index 0000000000000..7b4b32f665de6 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.errors.txt @@ -0,0 +1,13 @@ +/foo-const.ts(1,7): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,10): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,18): warning test-semantic-lint: String literal type 'foo' is forbidden. + + +==== /foo-const.ts (3 errors) ==== + const s: "foo" = "foo"; + ~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.js new file mode 100644 index 0000000000000..3743391a17e19 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.js @@ -0,0 +1,5 @@ +//// [foo-const.ts] +const s: "foo" = "foo"; + +//// [foo-const.js] +var s = "foo"; diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/semantic-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.errors.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.errors.txt new file mode 100644 index 0000000000000..26aff22d2534b --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.errors.txt @@ -0,0 +1,7 @@ +/foo-interface.ts(1,11): warning test-syntactic-lint: Identifier 'foo' is forbidden. + + +==== /foo-interface.ts (1 errors) ==== + interface Foo {a; b;} + ~~~ +!!! warning test-syntactic-lint: Identifier 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.js new file mode 100644 index 0000000000000..9bb521dc08570 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.js @@ -0,0 +1,4 @@ +//// [foo-interface.ts] +interface Foo {a; b;} + +//// [foo-interface.js] diff --git a/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.trace.txt new file mode 100644 index 0000000000000..9069e84844866 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multiLintLoads/syntactic-diagnostics.trace.txt @@ -0,0 +1,18 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.errors.txt b/tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.errors.txt new file mode 100644 index 0000000000000..f2f78f412a2cb --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.errors.txt @@ -0,0 +1,31 @@ +/foo-bar-const-interface.ts(1,11): warning test-multi-extension[IsNamedFoo]: Identifier 'foo' is forbidden. +/foo-bar-const-interface.ts(2,11): warning test-multi-extension[IsNamedBar]: Identifier 'bar' is forbidden. +/foo-bar-const-interface.ts(3,7): warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. +/foo-bar-const-interface.ts(3,10): warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. +/foo-bar-const-interface.ts(3,18): warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. +/foo-bar-const-interface.ts(4,5): warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. +/foo-bar-const-interface.ts(4,8): warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. +/foo-bar-const-interface.ts(4,16): warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. + + +==== /foo-bar-const-interface.ts (8 errors) ==== + interface Foo {b;} + ~~~ +!!! warning test-multi-extension[IsNamedFoo]: Identifier 'foo' is forbidden. + interface Bar {a;} + ~~~ +!!! warning test-multi-extension[IsNamedBar]: Identifier 'bar' is forbidden. + const f: "foo" = "foo"; + ~ +!!! warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueFoo]: String literal type 'foo' is forbidden. + let b: "bar" = "bar"; + ~ +!!! warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. + ~~~~~ +!!! warning test-multi-extension[IsValueBar]: String literal type 'bar' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.js b/tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.js new file mode 100644 index 0000000000000..7c7d27a43ddf5 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/multipleRulesFoundInSingleExtension/test.js @@ -0,0 +1,9 @@ +//// [foo-bar-const-interface.ts] +interface Foo {b;} +interface Bar {a;} +const f: "foo" = "foo"; +let b: "bar" = "bar"; + +//// [foo-bar-const-interface.js] +var f = "foo"; +var b = "bar"; diff --git a/tests/baselines/reference/LanguageServiceHost/passArguments/array.errors.txt b/tests/baselines/reference/LanguageServiceHost/passArguments/array.errors.txt new file mode 100644 index 0000000000000..111f82a3d9064 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/passArguments/array.errors.txt @@ -0,0 +1,10 @@ +/foo-interface.ts(1,16): warning test-extension-arguments: Identifier a is forbidden. +/foo-interface.ts(1,19): warning test-extension-arguments: Identifier b is forbidden. + + +==== /foo-interface.ts (2 errors) ==== + interface Foo {a; b;} + ~ +!!! warning test-extension-arguments: Identifier a is forbidden. + ~ +!!! warning test-extension-arguments: Identifier b is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/passArguments/array.js b/tests/baselines/reference/LanguageServiceHost/passArguments/array.js new file mode 100644 index 0000000000000..9bb521dc08570 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/passArguments/array.js @@ -0,0 +1,4 @@ +//// [foo-interface.ts] +interface Foo {a; b;} + +//// [foo-interface.js] diff --git a/tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.errors.txt b/tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.errors.txt new file mode 100644 index 0000000000000..9e6fc2a47ab5c --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.errors.txt @@ -0,0 +1,114 @@ +warning test-errors: Not allowed. +warning test-errors[Throws2](THROWS2): Not allowed. +error test-errors[Throws7]: Not allowed. +error test-errors[Throws8](THROWS8): Not allowed. +error test-errors[ThrowsOnAfterVisit](!!!): afterVisit failed with error: Error: Throws on afterVisit +error test-errors[ThrowsOnConstruct]: Lint construction failed: Throws on construct +error test-errors[ThrowsOnVisit](!!!): visit failed with error: Error: Throws on visit +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,1): error test-errors[Throws10](THROWS10): Not allowed. +/hello.ts(1,1): error test-errors[Throws12]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws4](THROWS4): Not allowed. +/hello.ts(1,1): warning test-errors[Throws6]: Not allowed. +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,1): error test-errors[Throws11]: Not allowed. +/hello.ts(1,1): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,1): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,1): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,9): error test-errors[Throws11]: Not allowed. +/hello.ts(1,9): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,9): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,9): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,13): error test-errors[Throws11]: Not allowed. +/hello.ts(1,13): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,13): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,13): error test-errors[Throws9](THROWS9): Not allowed. +/hello.ts(1,37): error test-errors[Throws11]: Not allowed. +/hello.ts(1,37): warning test-errors[Throws3](THROWS3): Not allowed. +/hello.ts(1,37): warning test-errors[Throws5]: Not allowed. +/hello.ts(1,37): error test-errors[Throws9](THROWS9): Not allowed. + + +!!! warning test-errors: Not allowed. +!!! warning test-errors[Throws2](THROWS2): Not allowed. +!!! error test-errors[Throws7]: Not allowed. +!!! error test-errors[Throws8](THROWS8): Not allowed. +!!! error test-errors[ThrowsOnAfterVisit](!!!): afterVisit failed with error: Error: Throws on afterVisit +!!! error test-errors[ThrowsOnConstruct]: Lint construction failed: Throws on construct +!!! error test-errors[ThrowsOnVisit](!!!): visit failed with error: Error: Throws on visit +==== /hello.ts (32 errors) ==== + console.log("Hello, world!");/*EOL*/ + ~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~ +!!! error test-errors[Throws10](THROWS10): Not allowed. + ~~~~~~~~~~ +!!! error test-errors[Throws12]: Not allowed. + ~~~~~~~~~~ +!!! warning test-errors[Throws4](THROWS4): Not allowed. + ~~~~~~~~~~ +!!! warning test-errors[Throws6]: Not allowed. + ~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + ~~~~~~~~~~~~~~~ +!!! error test-errors[Throws11]: Not allowed. + ~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws3](THROWS3): Not allowed. + ~~~~~~~~~~~~~~~ +!!! warning test-errors[Throws5]: Not allowed. + ~~~~~~~~~~~~~~~ +!!! error test-errors[Throws9](THROWS9): Not allowed. + +!!! error test-errors[Throws11]: Not allowed. + +!!! warning test-errors[Throws3](THROWS3): Not allowed. + +!!! warning test-errors[Throws5]: Not allowed. + +!!! error test-errors[Throws9](THROWS9): Not allowed. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.js b/tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.js new file mode 100644 index 0000000000000..b9f140e37bdde --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/reportsAllDIagnosticsFormats/test.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.errors.txt b/tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.errors.txt new file mode 100644 index 0000000000000..be0e109414197 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.errors.txt @@ -0,0 +1,21 @@ +/foo-bar-math.ts(1,7): warning test-multierrors(FOO): Identifier 'foo' is forbidden. +/foo-bar-math.ts(1,7): warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. +/foo-bar-math.ts(2,7): warning test-multierrors(BAR): Identifier 'bar' is forbidden. +/foo-bar-math.ts(2,7): warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. +/foo-bar-math.ts(3,7): warning test-multierrors[NoShortNames](SINGLE): Single character identifiers are forbidden + + +==== /foo-bar-math.ts (5 errors) ==== + const foo = 3; + ~~~ +!!! warning test-multierrors(FOO): Identifier 'foo' is forbidden. + ~~~ +!!! warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. + const bar = 4; + ~~~ +!!! warning test-multierrors(BAR): Identifier 'bar' is forbidden. + ~~~ +!!! warning test-multierrors[NoShortNames](SHORT): Short identifiers are forbidden. + const x = 3 * 4; + ~ +!!! warning test-multierrors[NoShortNames](SINGLE): Single character identifiers are forbidden \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.js b/tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.js new file mode 100644 index 0000000000000..f0e5216fe2bcf --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/reportsDiagnosticsWithShortnames/test.js @@ -0,0 +1,9 @@ +//// [foo-bar-math.ts] +const foo = 3; +const bar = 4; +const x = 3 * 4; + +//// [foo-bar-math.js] +var foo = 3; +var bar = 4; +var x = 3 * 4; diff --git a/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.js new file mode 100644 index 0000000000000..b9f140e37bdde --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.trace.txt new file mode 100644 index 0000000000000..acf7782714e0c --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/no-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.errors.txt b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.errors.txt new file mode 100644 index 0000000000000..7b4b32f665de6 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.errors.txt @@ -0,0 +1,13 @@ +/foo-const.ts(1,7): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,10): warning test-semantic-lint: String literal type 'foo' is forbidden. +/foo-const.ts(1,18): warning test-semantic-lint: String literal type 'foo' is forbidden. + + +==== /foo-const.ts (3 errors) ==== + const s: "foo" = "foo"; + ~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. + ~~~~~ +!!! warning test-semantic-lint: String literal type 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.js new file mode 100644 index 0000000000000..3743391a17e19 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.js @@ -0,0 +1,5 @@ +//// [foo-const.ts] +const s: "foo" = "foo"; + +//// [foo-const.js] +var s = "foo"; diff --git a/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.trace.txt new file mode 100644 index 0000000000000..acf7782714e0c --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/semanticLintLoads/w-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-semantic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-semantic-lint' from 'node_modules' folder. +File '/node_modules/test-semantic-lint.js' does not exist. +File '/node_modules/test-semantic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-semantic-lint/package.json'. +'package.json' has 'main' field 'main.js' that references '/node_modules/test-semantic-lint/main.js'. +File '/node_modules/test-semantic-lint/main.js' exist - use it as a name resolution result. +======== Module name 'test-semantic-lint' was successfully resolved to '/node_modules/test-semantic-lint/main.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.js new file mode 100644 index 0000000000000..b9f140e37bdde --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.trace.txt new file mode 100644 index 0000000000000..76f57ec088c21 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/no-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.errors.txt b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.errors.txt new file mode 100644 index 0000000000000..26aff22d2534b --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.errors.txt @@ -0,0 +1,7 @@ +/foo-interface.ts(1,11): warning test-syntactic-lint: Identifier 'foo' is forbidden. + + +==== /foo-interface.ts (1 errors) ==== + interface Foo {a; b;} + ~~~ +!!! warning test-syntactic-lint: Identifier 'foo' is forbidden. \ No newline at end of file diff --git a/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.js new file mode 100644 index 0000000000000..9bb521dc08570 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.js @@ -0,0 +1,4 @@ +//// [foo-interface.ts] +interface Foo {a; b;} + +//// [foo-interface.js] diff --git a/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.trace.txt b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.trace.txt new file mode 100644 index 0000000000000..76f57ec088c21 --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/syntacticLintLoads/w-diagnostics.trace.txt @@ -0,0 +1,9 @@ +======== Resolving module 'test-syntactic-lint' from '/tsconfig.json'. ======== +Module resolution kind is not specified, using 'NodeJs'. +Loading module 'test-syntactic-lint' from 'node_modules' folder. +File '/node_modules/test-syntactic-lint.js' does not exist. +File '/node_modules/test-syntactic-lint.jsx' does not exist. +Found 'package.json' at '/node_modules/test-syntactic-lint/package.json'. +'package.json' has 'main' field 'index.js' that references '/node_modules/test-syntactic-lint/index.js'. +File '/node_modules/test-syntactic-lint/index.js' exist - use it as a name resolution result. +======== Module name 'test-syntactic-lint' was successfully resolved to '/node_modules/test-syntactic-lint/index.js'. ======== \ No newline at end of file diff --git a/tests/cases/extensions/available/extension-api/index.ts b/tests/cases/extensions/available/extension-api/index.ts index 086fc445a1c38..38328fc44a437 100644 --- a/tests/cases/extensions/available/extension-api/index.ts +++ b/tests/cases/extensions/available/extension-api/index.ts @@ -1,2 +1,34 @@ import * as tsi from "typescript"; -// No APIs exposed \ No newline at end of file + +export abstract class SyntacticLintWalker implements tsi.LintWalker { + static "extension-kind": tsi.ExtensionKind.SyntacticLint = "syntactic-lint"; + protected ts: typeof tsi; + protected args: any; + protected host: tsi.CompilerHost; + protected program: tsi.Program; + constructor(state: {ts: typeof tsi, args: any, host: tsi.CompilerHost, program: tsi.Program}) { + this.ts = state.ts; + this.args = state.args; + this.host = state.host; + this.program = state.program; + } + abstract visit(node: tsi.Node, error: tsi.LintErrorMethod): boolean | void; +} + +export abstract class SemanticLintWalker implements tsi.LintWalker { + static "extension-kind": tsi.ExtensionKind.SemanticLint = "semantic-lint"; + protected ts: typeof tsi; + protected args: any; + protected host: tsi.CompilerHost; + protected program: tsi.Program; + protected checker: tsi.TypeChecker; + constructor(state: {ts: typeof tsi, args: any, host: tsi.CompilerHost, program: tsi.Program, checker: tsi.TypeChecker}) { + this.ts = state.ts; + this.args = state.args; + this.host = state.host; + this.program = state.program; + this.checker = state.checker; + } + abstract visit(node: tsi.Node, error: tsi.LintErrorMethod): boolean | void; +} + diff --git a/tests/cases/extensions/available/test-errors/index.ts b/tests/cases/extensions/available/test-errors/index.ts new file mode 100644 index 0000000000000..1b9329523273c --- /dev/null +++ b/tests/cases/extensions/available/test-errors/index.ts @@ -0,0 +1,117 @@ +import {SyntacticLintWalker} from "extension-api"; + +export default class Throws extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error("Not allowed."); + return false; + } +} + +export class Throws2 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error("THROWS2", "Not allowed."); + return false; + } +} + +export class Throws3 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error("THROWS3", "Not allowed.", node); + return false; + } +} + +export class Throws4 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error("THROWS4", "Not allowed.", 0, 10); + return false; + } +} + +export class Throws5 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error("Not allowed.", node); + return false; + } +} + +export class Throws6 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error("Not allowed.", 0, 10); + return false; + } +} + +export class Throws7 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error(this.ts.DiagnosticCategory.Error, "Not allowed."); + return false; + } +} + +export class Throws8 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error(this.ts.DiagnosticCategory.Error, "THROWS8", "Not allowed."); + return false; + } +} + +export class Throws9 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error(this.ts.DiagnosticCategory.Error, "THROWS9", "Not allowed.", node); + return false; + } +} + +export class Throws10 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error(this.ts.DiagnosticCategory.Error, "THROWS10", "Not allowed.", 0, 10); + return false; + } +} + +export class Throws11 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error(this.ts.DiagnosticCategory.Error, "Not allowed.", node); + return false; + } +} + +export class Throws12 extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + error(this.ts.DiagnosticCategory.Error, "Not allowed.", 0, 10); + return false; + } +} + +export class ThrowsOnConstruct extends SyntacticLintWalker { + constructor(state) { super(state); throw new Error("Throws on construct"); } + visit(node, error) {} +} + +export class ThrowsOnVisit extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + throw new Error("Throws on visit"); + } +} + +export class ThrowsOnAfterVisit extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { return false; } + afterVisit(node, error) { + throw new Error("Throws on afterVisit"); + } +} \ No newline at end of file diff --git a/tests/cases/extensions/available/test-errors/package.json b/tests/cases/extensions/available/test-errors/package.json new file mode 100644 index 0000000000000..c61e748bd93cb --- /dev/null +++ b/tests/cases/extensions/available/test-errors/package.json @@ -0,0 +1,7 @@ +{ + "name": "test-errors", + "version": "1.0.0", + "description": "", + "main": "index.js", + "author": "" +} \ No newline at end of file diff --git a/tests/cases/extensions/available/test-extension-arguments/index.ts b/tests/cases/extensions/available/test-extension-arguments/index.ts new file mode 100644 index 0000000000000..44b0b0b0ca460 --- /dev/null +++ b/tests/cases/extensions/available/test-extension-arguments/index.ts @@ -0,0 +1,14 @@ +import {SyntacticLintWalker} from "extension-api"; + +export default class IsNamedX extends SyntacticLintWalker { + constructor(state) { super(state); } + visit(node, error) { + if (node.kind === this.ts.SyntaxKind.Identifier) { + for (let i = 0; i Date: Fri, 5 Aug 2016 14:33:53 -0700 Subject: [PATCH 3/3] Move lint diagnostics to their own calls and add cancellation token --- src/compiler/extensions.ts | 4 +- src/compiler/program.ts | 47 +++++++++--- src/compiler/tsc.ts | 4 +- src/compiler/types.ts | 2 + src/harness/extensionRunner.ts | 8 ++- src/harness/harnessLanguageService.ts | 6 ++ src/server/client.ts | 8 +++ src/server/session.ts | 16 +++++ src/services/services.ts | 16 +++++ src/services/shims.ts | 27 ++++--- .../available/extension-api/index.ts | 72 ++++++++++--------- 11 files changed, 152 insertions(+), 58 deletions(-) diff --git a/src/compiler/extensions.ts b/src/compiler/extensions.ts index 3d887dd6800ac..cce94dc47e6e3 100644 --- a/src/compiler/extensions.ts +++ b/src/compiler/extensions.ts @@ -101,12 +101,12 @@ namespace ts { export interface SyntacticLintProviderStatic extends BaseProviderStatic { readonly ["extension-kind"]: ExtensionKind.SyntacticLint; - new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program}): LintWalker; + new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program, token: CancellationToken}): LintWalker; } export interface SemanticLintProviderStatic extends BaseProviderStatic { readonly ["extension-kind"]: ExtensionKind.SemanticLint; - new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program, checker: TypeChecker}): LintWalker; + new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program, token: CancellationToken, checker: TypeChecker}): LintWalker; } export namespace ExtensionKind { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bcf7b4efb8f36..6e98b6a21f8b0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1191,9 +1191,11 @@ namespace ts { getSourceFiles: () => files, getCompilerOptions: () => options, getSyntacticDiagnostics, + getSyntacticLintDiagnostics, getOptionsDiagnostics, getGlobalDiagnostics, getSemanticDiagnostics, + getSemanticLintDiagnostics, getDeclarationDiagnostics, getTypeChecker, getClassifiableNames, @@ -1502,10 +1504,18 @@ namespace ts { return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); } + function getSyntacticLintDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSyntacticLintDiagnosticsForFile, cancellationToken); + } + function getSemanticDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); } + function getSemanticLintDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSemanticLintDiagnosticsForFile, cancellationToken); + } + function getDeclarationDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { const options = program.getCompilerOptions(); // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) @@ -1517,7 +1527,7 @@ namespace ts { } } - function performLintPassOnFile(sourceFile: SourceFile, kind: ExtensionKind.SyntacticLint | ExtensionKind.SemanticLint): Diagnostic[] | undefined { + function performLintPassOnFile(sourceFile: SourceFile, token: CancellationToken, kind: ExtensionKind.SyntacticLint | ExtensionKind.SemanticLint): Diagnostic[] | undefined { const lints = extensionCache.getCompilerExtensions()[kind]; if (!lints || !lints.length) { return; @@ -1534,9 +1544,12 @@ namespace ts { const checker = getTypeChecker(); startExtensionProfile(profilingEnabled, name, "construct"); try { - walker = new (ctor as SemanticLintProviderStatic)({ ts, checker, args, host, program }); + walker = new (ctor as SemanticLintProviderStatic)({ ts, args, host, program, token, checker }); } catch (e) { + if (e instanceof OperationCanceledException) { + throw e; + } diagnostics.push(createExtensionDiagnostic(name, `Lint construction failed: ${(e as Error).message}`, sourceFile, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); } completeExtensionProfile(profilingEnabled, name, "construct"); @@ -1544,9 +1557,12 @@ namespace ts { else if (kind === ExtensionKind.SyntacticLint) { startExtensionProfile(profilingEnabled, name, "construct"); try { - walker = new (ctor as SyntacticLintProviderStatic)({ ts, args, host, program }); + walker = new (ctor as SyntacticLintProviderStatic)({ ts, args, host, program, token }); } catch (e) { + if (e instanceof OperationCanceledException) { + throw e; + } diagnostics.push(createExtensionDiagnostic(name, `Lint construction failed: ${(e as Error).message}`, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); } completeExtensionProfile(profilingEnabled, name, "construct"); @@ -1573,6 +1589,9 @@ namespace ts { activeLint.accepted = !activeLint.walker.visit(node, error); } catch (e) { + if (e instanceof OperationCanceledException) { + throw e; + } activeLint.errored = true; diagnostics.push(createExtensionDiagnostic(errorQualifiedName("!!!"), `visit failed with error: ${e}`, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); } @@ -1614,6 +1633,9 @@ namespace ts { activeLint.walker.afterVisit(node, error); } catch (e) { + if (e instanceof OperationCanceledException) { + throw e; + } activeLint.errored = true; diagnostics.push(createExtensionDiagnostic(errorQualifiedName("!!!"), `afterVisit failed with error: ${e}`, /*sourceFile*/undefined, /*start*/undefined, /*length*/undefined, DiagnosticCategory.Error)); } @@ -1766,13 +1788,17 @@ namespace ts { } function getSyntacticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + return sourceFile.parseDiagnostics; + } + + function getSyntacticLintDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { if (!sourceFile.isDeclarationFile) { - const lintDiagnostics = performLintPassOnFile(sourceFile, ExtensionKind.SyntacticLint); + const lintDiagnostics = performLintPassOnFile(sourceFile, cancellationToken, ExtensionKind.SyntacticLint); if (lintDiagnostics && lintDiagnostics.length) { - return sourceFile.parseDiagnostics.concat(lintDiagnostics); + return lintDiagnostics; } } - return sourceFile.parseDiagnostics; + return []; } function runWithCancellationToken(func: () => T): T { @@ -1812,9 +1838,14 @@ namespace ts { typeChecker.getDiagnostics(sourceFile, cancellationToken); const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); - const lintDiagnostics = (!sourceFile.isDeclarationFile) ? (performLintPassOnFile(sourceFile, ExtensionKind.SemanticLint) || []) : []; - return bindDiagnostics.concat(checkDiagnostics).concat(fileProcessingDiagnosticsInFile).concat(programDiagnosticsInFile).concat(lintDiagnostics); + return bindDiagnostics.concat(checkDiagnostics).concat(fileProcessingDiagnosticsInFile).concat(programDiagnosticsInFile); + }); + } + + function getSemanticLintDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] { + return runWithCancellationToken(() => { + return (!sourceFile.isDeclarationFile) ? (performLintPassOnFile(sourceFile, cancellationToken, ExtensionKind.SemanticLint) || []) : []; }); } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 9ecde9262f5f2..a77f16ba53ed5 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -605,7 +605,7 @@ namespace ts { let diagnostics: Diagnostic[]; // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics(); + diagnostics = program.getSyntacticDiagnostics().concat(program.getSyntacticLintDiagnostics()); // Count warnings/messages and ignore them for determining continued error reporting const nonErrorCount = countWhere(diagnostics, d => d.category !== DiagnosticCategory.Error); @@ -618,7 +618,7 @@ namespace ts { const nonErrorCount = countWhere(diagnostics, d => d.category !== DiagnosticCategory.Error); if (diagnostics.length === nonErrorCount) { - diagnostics = diagnostics.concat(program.getSemanticDiagnostics()); + diagnostics = diagnostics.concat(program.getSemanticDiagnostics()).concat(program.getSemanticLintDiagnostics()); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dc750e2c9dff5..5aa84d267826c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1746,6 +1746,8 @@ namespace ts { getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getSyntacticLintDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + getSemanticLintDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /** diff --git a/src/harness/extensionRunner.ts b/src/harness/extensionRunner.ts index ca7ab8c4a6d9b..5ef3f02208b41 100644 --- a/src/harness/extensionRunner.ts +++ b/src/harness/extensionRunner.ts @@ -153,10 +153,12 @@ class ExtensionRunner extends RunnerBase { const service = ts.createLanguageService(host); const fileResults: Harness.Compiler.GeneratedFile[] = []; - const diagnostics = ts.concatenate(ts.concatenate( + const diagnostics = ts.concatenate(ts.concatenate(ts.concatenate(ts.concatenate( service.getProgramDiagnostics(), ts.flatten(ts.map(typescriptFiles, fileName => service.getSyntacticDiagnostics(this.getCanonicalFileName(fileName))))), - ts.flatten(ts.map(typescriptFiles, fileName => service.getSemanticDiagnostics(this.getCanonicalFileName(fileName))))); + ts.flatten(ts.map(typescriptFiles, fileName => service.getSyntacticLintDiagnostics(this.getCanonicalFileName(fileName))))), + ts.flatten(ts.map(typescriptFiles, fileName => service.getSemanticDiagnostics(this.getCanonicalFileName(fileName))))), + ts.flatten(ts.map(typescriptFiles, fileName => service.getSemanticLintDiagnostics(this.getCanonicalFileName(fileName))))); const emitResult = service.getProgram().emit(/*targetSourceFile*/undefined, writeFile); @@ -178,7 +180,7 @@ class ExtensionRunner extends RunnerBase { const self = this; const program = ts.createProgram(typescriptFiles, options, this.mockHost); const fileResults: Harness.Compiler.GeneratedFile[] = []; - const diagnostics = ts.getPreEmitDiagnostics(program); + const diagnostics = ts.concatenate(ts.getPreEmitDiagnostics(program), ts.concatenate(program.getSyntacticLintDiagnostics(), program.getSemanticLintDiagnostics())); const emitResult = program.emit(/*targetSourceFile*/undefined, writeFile); const allDiagnostics = ts.sortAndDeduplicateDiagnostics(ts.concatenate(diagnostics, emitResult.diagnostics)); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index fdd2f6602ecb5..08a80645d0132 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -360,9 +360,15 @@ namespace Harness.LanguageService { getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] { return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); } + getSyntacticLintDiagnostics(fileName: string): ts.Diagnostic[] { + return unwrapJSONCallResult(this.shim.getSyntacticLintDiagnostics(fileName)); + } getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); } + getSemanticLintDiagnostics(fileName: string): ts.Diagnostic[] { + return unwrapJSONCallResult(this.shim.getSemanticLintDiagnostics(fileName)); + } getCompilerOptionsDiagnostics(): ts.Diagnostic[] { return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); } diff --git a/src/server/client.ts b/src/server/client.ts index d12c50459b6fe..8a6552ffa399f 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -390,10 +390,18 @@ namespace ts.server { throw new Error("Not Implemented Yet."); } + getSyntacticLintDiagnostics(fileName: string): Diagnostic[] { + throw new Error("Not Implemented Yet."); + } + getSemanticDiagnostics(fileName: string): Diagnostic[] { throw new Error("Not Implemented Yet."); } + getSemanticLintDiagnostics(fileName: string): Diagnostic[] { + throw new Error("Not Implemented Yet."); + } + getCompilerOptionsDiagnostics(): Diagnostic[] { return this.getProgramDiagnostics(); } diff --git a/src/server/session.ts b/src/server/session.ts index 7e1ca81e2af8c..766899daff0e0 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -112,7 +112,9 @@ namespace ts.server { export const Geterr = "geterr"; export const GeterrForProject = "geterrForProject"; export const SemanticDiagnosticsSync = "semanticDiagnosticsSync"; + export const SemanticLintDiagnosticsSync = "semanticLintDiagnosticsSync"; export const SyntacticDiagnosticsSync = "syntacticDiagnosticsSync"; + export const SyntacticLintDiagnosticsSync = "syntacticLintDiagnosticsSync"; export const NavBar = "navbar"; export const Navto = "navto"; export const Occurrences = "occurrences"; @@ -404,10 +406,18 @@ namespace ts.server { return this.getDiagnosticsWorker(args, (project, file) => project.compilerService.languageService.getSyntacticDiagnostics(file)); } + private getSyntacticLintDiagnosticsSync(args: protocol.FileRequestArgs): protocol.Diagnostic[] { + return this.getDiagnosticsWorker(args, (project, file) => project.compilerService.languageService.getSyntacticLintDiagnostics(file)); + } + private getSemanticDiagnosticsSync(args: protocol.FileRequestArgs): protocol.Diagnostic[] { return this.getDiagnosticsWorker(args, (project, file) => project.compilerService.languageService.getSemanticDiagnostics(file)); } + private getSemanticLintDiagnosticsSync(args: protocol.FileRequestArgs): protocol.Diagnostic[] { + return this.getDiagnosticsWorker(args, (project, file) => project.compilerService.languageService.getSemanticLintDiagnostics(file)); + } + private getDocumentHighlights(line: number, offset: number, fileName: string, filesToSearch: string[]): protocol.DocumentHighlightsItem[] { fileName = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(fileName); @@ -1132,9 +1142,15 @@ namespace ts.server { [CommandNames.SemanticDiagnosticsSync]: (request: protocol.FileRequest) => { return this.requiredResponse(this.getSemanticDiagnosticsSync(request.arguments)); }, + [CommandNames.SemanticLintDiagnosticsSync]: (request: protocol.FileRequest) => { + return this.requiredResponse(this.getSemanticLintDiagnosticsSync(request.arguments)); + }, [CommandNames.SyntacticDiagnosticsSync]: (request: protocol.FileRequest) => { return this.requiredResponse(this.getSyntacticDiagnosticsSync(request.arguments)); }, + [CommandNames.SyntacticLintDiagnosticsSync]: (request: protocol.FileRequest) => { + return this.requiredResponse(this.getSyntacticLintDiagnosticsSync(request.arguments)); + }, [CommandNames.Geterr]: (request: protocol.Request) => { const geterrArgs = request.arguments; return { response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false }; diff --git a/src/services/services.ts b/src/services/services.ts index 7805fd19e03b9..50c9e4a38c507 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1179,7 +1179,9 @@ namespace ts { cleanupSemanticCache(): void; getSyntacticDiagnostics(fileName: string): Diagnostic[]; + getSyntacticLintDiagnostics(fileName: string): Diagnostic[]; getSemanticDiagnostics(fileName: string): Diagnostic[]; + getSemanticLintDiagnostics(fileName: string): Diagnostic[]; /** * @deprecated Use getProgramDiagnostics instead. @@ -3300,6 +3302,12 @@ namespace ts { return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken); } + function getSyntacticLintDiagnostics(fileName: string) { + synchronizeHostData(); + + return program.getSyntacticLintDiagnostics(getValidSourceFile(fileName), cancellationToken); + } + /** * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors * If '-d' enabled, report both semantic and emitter errors @@ -3322,6 +3330,12 @@ namespace ts { return concatenate(semanticDiagnostics, declarationDiagnostics); } + function getSemanticLintDiagnostics(fileName: string) { + synchronizeHostData(); + + return program.getSemanticLintDiagnostics(getValidSourceFile(fileName), cancellationToken); + } + function getProgramDiagnostics() { synchronizeHostData(); return program.getOptionsDiagnostics(cancellationToken).concat( @@ -8282,7 +8296,9 @@ namespace ts { dispose, cleanupSemanticCache, getSyntacticDiagnostics, + getSyntacticLintDiagnostics, getSemanticDiagnostics, + getSemanticLintDiagnostics, getCompilerOptionsDiagnostics: getProgramDiagnostics, getProgramDiagnostics, getSyntacticClassifications, diff --git a/src/services/shims.ts b/src/services/shims.ts index a73133940bd6e..f9bce082b8b32 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -127,7 +127,9 @@ namespace ts { cleanupSemanticCache(): void; getSyntacticDiagnostics(fileName: string): string; + getSyntacticLintDiagnostics(fileName: string): string; getSemanticDiagnostics(fileName: string): string; + getSemanticLintDiagnostics(fileName: string): string; getCompilerOptionsDiagnostics(): string; getProgramDiagnostics(): string; @@ -673,22 +675,29 @@ namespace ts { ); } - public getSyntacticDiagnostics(fileName: string): string { + private getSpecificDiagnostics(fileName: string, methodName: "getSemanticDiagnostics" | "getSemanticLintDiagnostics" | "getSyntacticDiagnostics" | "getSyntacticLintDiagnostics") { return this.forwardJSONCall( - `getSyntacticDiagnostics('${fileName}')`, + `${methodName}('${fileName}')`, () => { - const diagnostics = this.languageService.getSyntacticDiagnostics(fileName); + const diagnostics = (this.languageService as any as ts.Map<(fileName: string) => Diagnostic[]>)[methodName](fileName); return this.realizeDiagnostics(diagnostics); }); } + public getSyntacticDiagnostics(fileName: string): string { + return this.getSpecificDiagnostics(fileName, "getSyntacticDiagnostics"); + } + + public getSyntacticLintDiagnostics(fileName: string): string { + return this.getSpecificDiagnostics(fileName, "getSyntacticLintDiagnostics"); + } + public getSemanticDiagnostics(fileName: string): string { - return this.forwardJSONCall( - `getSemanticDiagnostics('${fileName}')`, - () => { - const diagnostics = this.languageService.getSemanticDiagnostics(fileName); - return this.realizeDiagnostics(diagnostics); - }); + return this.getSpecificDiagnostics(fileName, "getSemanticDiagnostics"); + } + + public getSemanticLintDiagnostics(fileName: string): string { + return this.getSpecificDiagnostics(fileName, "getSemanticLintDiagnostics"); } public getCompilerOptionsDiagnostics(): string { diff --git a/tests/cases/extensions/available/extension-api/index.ts b/tests/cases/extensions/available/extension-api/index.ts index 38328fc44a437..aa4dd3bb4d837 100644 --- a/tests/cases/extensions/available/extension-api/index.ts +++ b/tests/cases/extensions/available/extension-api/index.ts @@ -1,34 +1,38 @@ -import * as tsi from "typescript"; - -export abstract class SyntacticLintWalker implements tsi.LintWalker { - static "extension-kind": tsi.ExtensionKind.SyntacticLint = "syntactic-lint"; - protected ts: typeof tsi; - protected args: any; - protected host: tsi.CompilerHost; - protected program: tsi.Program; - constructor(state: {ts: typeof tsi, args: any, host: tsi.CompilerHost, program: tsi.Program}) { - this.ts = state.ts; - this.args = state.args; - this.host = state.host; - this.program = state.program; - } - abstract visit(node: tsi.Node, error: tsi.LintErrorMethod): boolean | void; -} - -export abstract class SemanticLintWalker implements tsi.LintWalker { - static "extension-kind": tsi.ExtensionKind.SemanticLint = "semantic-lint"; - protected ts: typeof tsi; - protected args: any; - protected host: tsi.CompilerHost; - protected program: tsi.Program; - protected checker: tsi.TypeChecker; - constructor(state: {ts: typeof tsi, args: any, host: tsi.CompilerHost, program: tsi.Program, checker: tsi.TypeChecker}) { - this.ts = state.ts; - this.args = state.args; - this.host = state.host; - this.program = state.program; - this.checker = state.checker; - } - abstract visit(node: tsi.Node, error: tsi.LintErrorMethod): boolean | void; -} - +import * as tsi from "typescript"; + +export abstract class SyntacticLintWalker implements tsi.LintWalker { + static "extension-kind": tsi.ExtensionKind.SyntacticLint = "syntactic-lint"; + protected ts: typeof tsi; + protected args: any; + protected host: tsi.CompilerHost; + protected program: tsi.Program; + protected token: tsi.CancellationToken; + constructor(state: {ts: typeof tsi, args: any, host: tsi.CompilerHost, program: tsi.Program, token: tsi.CancellationToken}) { + this.ts = state.ts; + this.args = state.args; + this.host = state.host; + this.program = state.program; + this.token = state.token; + } + abstract visit(node: tsi.Node, error: tsi.LintErrorMethod): boolean | void; +} + +export abstract class SemanticLintWalker implements tsi.LintWalker { + static "extension-kind": tsi.ExtensionKind.SemanticLint = "semantic-lint"; + protected ts: typeof tsi; + protected args: any; + protected host: tsi.CompilerHost; + protected program: tsi.Program; + protected token: tsi.CancellationToken; + protected checker: tsi.TypeChecker; + constructor(state: {ts: typeof tsi, args: any, host: tsi.CompilerHost, program: tsi.Program, token: tsi.CancellationToken, checker: tsi.TypeChecker}) { + this.ts = state.ts; + this.args = state.args; + this.host = state.host; + this.program = state.program; + this.checker = state.checker; + this.token = state.token; + } + abstract visit(node: tsi.Node, error: tsi.LintErrorMethod): boolean | void; +} +