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..dd78702b67f85
--- /dev/null
+++ b/src/compiler/extensions.ts
@@ -0,0 +1,226 @@
+namespace ts {
+
+ export interface BaseProviderStatic {
+ readonly ["extension-kind"]: ExtensionKind;
+ new (state: {ts: typeof ts, args: any}): any;
+ }
+
+ export interface LanguageServiceHost {} // The members for these interfaces are provided in the services layer
+ export interface LanguageService {}
+ export interface LanguageServiceProvider {}
+ export interface DocumentRegistry {}
+
+ export interface LanguageServiceProviderStatic extends BaseProviderStatic {
+ readonly ["extension-kind"]: ExtensionKind.LanguageService;
+ new (state: { ts: typeof ts, args: any, host: LanguageServiceHost, service: LanguageService, registry: DocumentRegistry }): LanguageServiceProvider;
+ }
+
+ export namespace ExtensionKind {
+ export const LanguageService: "language-service" = "language-service";
+ export type LanguageService = "language-service";
+ }
+ export type ExtensionKind = ExtensionKind.LanguageService;
+
+ export interface ExtensionCollectionMap {
+ "language-service"?: LanguageServiceExtension[];
+ [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 interface LanguageServiceExtension extends ExtensionBase {
+ kind: ExtensionKind.LanguageService;
+ ctor: LanguageServiceProviderStatic;
+ }
+
+ export type Extension = LanguageServiceExtension;
+
+ 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) {
+ case ExtensionKind.LanguageService:
+ 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 LanguageServiceExtension).ctor = potentialExtension as LanguageServiceProviderStatic;
+ 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
+ (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..55ce4efd6693a 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.
*/
@@ -1246,6 +1251,86 @@ namespace ts {
dispose(): void;
}
+ export interface LanguageServiceProvider {
+ // Overrides
+
+ // A plugin can implement one of the override methods to replace the results that would
+ // be returned by the TypeScript language service. If a plugin returns a defined result
+ // (that is, is not undefined) then that result is used instead of invoking the
+ // corresponding TypeScript method. If multiple plugins are registered, they are
+ // consulted in the order they are returned from the program. The first defined result
+ // returned by a plugin is used and no other plugin overrides are consulted.
+
+ getProgramDiagnostics?(): Diagnostic[];
+ getSyntacticDiagnostics?(fileName: string): Diagnostic[];
+ getSemanticDiagnostics?(fileName: string): Diagnostic[];
+ getEncodedSyntacticClassifications?(fileName: string, span: TextSpan): Classifications;
+ getEncodedSemanticClassifications?(fileName: string, span: TextSpan): Classifications;
+ getCompletionsAtPosition?(fileName: string, position: number): CompletionInfo;
+ getCompletionEntryDetails?(fileName: string, position: number, entryName: string): CompletionEntryDetails;
+ getQuickInfoAtPosition?(fileName: string, position: number): QuickInfo;
+ getNameOrDottedNameSpan?(fileName: string, startPos: number, endPos: number): TextSpan;
+ getBreakpointStatementAtPosition?(fileName: string, position: number): TextSpan;
+ getSignatureHelpItems?(fileName: string, position: number): SignatureHelpItems;
+ getRenameInfo?(fileName: string, position: number): RenameInfo;
+ findRenameLocations?(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
+ getDefinitionAtPosition?(fileName: string, position: number): DefinitionInfo[];
+ getTypeDefinitionAtPosition?(fileName: string, position: number): DefinitionInfo[];
+ getReferencesAtPosition?(fileName: string, position: number): ReferenceEntry[];
+ findReferences?(fileName: string, position: number): ReferencedSymbol[];
+ getDocumentHighlights?(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[];
+ getNavigateToItems?(searchValue: string, maxResultCount: number): NavigateToItem[];
+ getNavigationBarItems?(fileName: string): NavigationBarItem[];
+ getOutliningSpans?(fileName: string): OutliningSpan[];
+ getTodoComments?(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[];
+ getBraceMatchingAtPosition?(fileName: string, position: number): TextSpan[];
+ getIndentationAtPosition?(fileName: string, position: number, options: EditorOptions): number;
+ getFormattingEditsForRange?(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[];
+ getFormattingEditsForDocument?(fileName: string, options: FormatCodeOptions): TextChange[];
+ getFormattingEditsAfterKeystroke?(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[];
+ getDocCommentTemplateAtPosition?(fileName: string, position: number): TextInsertion;
+
+ // Filters
+
+ // A plugin can implement one of the filter methods to augment, extend or modify a result
+ // prior to the host receiving it. The TypeScript language service is invoked and the
+ // result is passed to the plugin as the value of the previous parameter. If more than one
+ // plugin is registered, the plugins are consulted in the order they are returned from the
+ // program. The value passed in as previous is the result returned by the prior plugin. If a
+ // plugin returns undefined, the result passed in as previous is used and the undefined
+ // result is ignored. All plugins are consulted before the result is returned to the host.
+ // If a plugin overrides behavior of the method, no filter methods are consulted.
+
+ getProgramDiagnosticsFilter?(previous: Diagnostic[]): Diagnostic[];
+ getSyntacticDiagnosticsFilter?(fileName: string, previous: Diagnostic[]): Diagnostic[];
+ getSemanticDiagnosticsFilter?(fileName: string, previous: Diagnostic[]): Diagnostic[];
+ getEncodedSyntacticClassificationsFilter?(fileName: string, span: TextSpan, previous: Classifications): Classifications;
+ getEncodedSemanticClassificationsFilter?(fileName: string, span: TextSpan, previous: Classifications): Classifications;
+ getCompletionsAtPositionFilter?(fileName: string, position: number, previous: CompletionInfo): CompletionInfo;
+ getCompletionEntryDetailsFilter?(fileName: string, position: number, entryName: string, previous: CompletionEntryDetails): CompletionEntryDetails;
+ getQuickInfoAtPositionFilter?(fileName: string, position: number, previous: QuickInfo): QuickInfo;
+ getNameOrDottedNameSpanFilter?(fileName: string, startPos: number, endPos: number, previous: TextSpan): TextSpan;
+ getBreakpointStatementAtPositionFilter?(fileName: string, position: number, previous: TextSpan): TextSpan;
+ getSignatureHelpItemsFilter?(fileName: string, position: number, previous: SignatureHelpItems): SignatureHelpItems;
+ getRenameInfoFilter?(fileName: string, position: number, previous: RenameInfo): RenameInfo;
+ findRenameLocationsFilter?(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, previous: RenameLocation[]): RenameLocation[];
+ getDefinitionAtPositionFilter?(fileName: string, position: number, previous: DefinitionInfo[]): DefinitionInfo[];
+ getTypeDefinitionAtPositionFilter?(fileName: string, position: number, previous: DefinitionInfo[]): DefinitionInfo[];
+ getReferencesAtPositionFilter?(fileName: string, position: number, previous: ReferenceEntry[]): ReferenceEntry[];
+ findReferencesFilter?(fileName: string, position: number, previous: ReferencedSymbol[]): ReferencedSymbol[];
+ getDocumentHighlightsFilter?(fileName: string, position: number, filesToSearch: string[], previous: DocumentHighlights[]): DocumentHighlights[];
+ getNavigateToItemsFilter?(searchValue: string, maxResultCount: number, previous: NavigateToItem[]): NavigateToItem[];
+ getNavigationBarItemsFilter?(fileName: string, previous: NavigationBarItem[]): NavigationBarItem[];
+ getOutliningSpansFilter?(fileName: string, previous: OutliningSpan[]): OutliningSpan[];
+ getTodoCommentsFilter?(fileName: string, descriptors: TodoCommentDescriptor[], previous: TodoComment[]): TodoComment[];
+ getBraceMatchingAtPositionFilter?(fileName: string, position: number, previous: TextSpan[]): TextSpan[];
+ getIndentationAtPositionFilter?(fileName: string, position: number, options: EditorOptions, previous: number): number;
+ getFormattingEditsForRangeFilter?(fileName: string, start: number, end: number, options: FormatCodeOptions, previous: TextChange[]): TextChange[];
+ getFormattingEditsForDocumentFilter?(fileName: string, options: FormatCodeOptions, previous: TextChange[]): TextChange[];
+ getFormattingEditsAfterKeystrokeFilter?(fileName: string, position: number, key: string, options: FormatCodeOptions, previous: TextChange[]): TextChange[];
+ getDocCommentTemplateAtPositionFilter?(fileName: string, position: number, previous: TextInsertion): TextInsertion;
+ }
+
export interface Classifications {
spans: number[];
endOfLineState: EndOfLineState;
@@ -1826,6 +1911,7 @@ namespace ts {
version: string;
scriptSnapshot: IScriptSnapshot;
scriptKind: ScriptKind;
+ isRoot: boolean;
}
interface DocumentRegistryEntry {
@@ -1902,7 +1988,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 +1999,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 +2007,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 +2032,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);
}
});
@@ -3019,11 +3106,84 @@ namespace ts {
}
export function createLanguageService(host: LanguageServiceHost,
+ documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
+ const baseService = createUnextendedLanguageService(host, documentRegistry);
+ const extensions = baseService.getProgram().getCompilerExtensions()["language-service"];
+ const instantiatedExtensions = map(extensions, extension => new extension.ctor({ ts, host, service: baseService, registry: documentRegistry, args: extension.args }));
+ const extensionCount = instantiatedExtensions && instantiatedExtensions.length;
+
+ function wrap(key: string): Function {
+ if (extensionCount) {
+ return (...args: any[]) => {
+ let result: any;
+ for (let i = 0; i < extensionCount; i++) {
+ const extension = instantiatedExtensions[i];
+ if ((extension as any)[key]) {
+ const temp = (extension as any)[key](...args);
+ if (temp !== undefined) {
+ result = temp;
+ break;
+ }
+ }
+ }
+ result = result !== undefined ? result : (baseService as any)[key](...args);
+ const filterKey = `${key}Filter`;
+ for (let i = 0; i < extensionCount; i++) {
+ const extension = instantiatedExtensions[i];
+ if ((extension as any)[filterKey]) {
+ const temp = (extension as any)[filterKey](...args, result);
+ if (temp !== undefined) {
+ result = temp;
+ }
+ }
+ }
+ return result;
+ };
+ }
+ return (baseService as any)[key];
+ }
+
+ function buildWrappedService(underlyingMembers: Map, wrappedMembers: string[]): LanguageService {
+ // Add wrapped members to map
+ forEach(wrappedMembers, member => {
+ underlyingMembers[member] = wrap(member);
+ });
+ // Map getProgramDiagnostics to deprecated getCompilerOptionsDiagnostics
+ underlyingMembers["getCompilerOptionsDiagnostics"] = underlyingMembers["getProgramDiagnostics"];
+ return underlyingMembers as LanguageService;
+ }
+
+ return buildWrappedService({
+ cleanupSemanticCache: () => baseService.cleanupSemanticCache(),
+ getSyntacticClassifications: (fileName: string, span: TextSpan) => baseService.getSyntacticClassifications(fileName, span),
+ getSemanticClassifications: (fileName: string, span: TextSpan) => baseService.getSemanticClassifications(fileName, span),
+ getOccurrencesAtPosition: (fileName: string, position: number) => baseService.getOccurrencesAtPosition(fileName, position),
+ isValidBraceCompletionAtPosition: (fileName: string, pos: number, openingBrace: number) => baseService.isValidBraceCompletionAtPosition(fileName, pos, openingBrace),
+ getEmitOutput: (fileName: string) => baseService.getEmitOutput(fileName),
+ getProgram: () => baseService.getProgram(),
+ getNonBoundSourceFile: (fileName: string) => baseService.getNonBoundSourceFile(fileName),
+ dispose: () => baseService.dispose(),
+ }, [
+ "getSyntacticDiagnostics", "getSemanticDiagnostics", "getProgramDiagnostics",
+ "getEncodedSyntacticClassifications", "getEncodedSemanticClassifications", "getCompletionsAtPosition",
+ "getCompletionEntryDetails", "getQuickInfoAtPosition", "getNameOrDottedNameSpan",
+ "getBreakpointStatementAtPosition", "getSignatureHelpItems", "getRenameInfo",
+ "findRenameLocations", "getDefinitionAtPosition", "getTypeDefinitionAtPosition",
+ "getReferencesAtPosition", "findReferences", "getDocumentHighlights",
+ "getNavigateToItems", "getNavigationBarItems", "getOutliningSpans",
+ "getTodoComments", "getBraceMatchingAtPosition", "getIndentationAtPosition",
+ "getFormattingEditsForRange", "getFormattingEditsForDocument", "getFormattingEditsAfterKeystroke",
+ "getDocCommentTemplateAtPosition"
+ ]);
+ }
+
+ export function createUnextendedLanguageService(host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
let ruleProvider: formatting.RulesProvider;
let program: Program;
+ let extensionCache: ExtensionCache;
let lastProjectVersion: string;
const useCaseSensitivefileNames = false;
@@ -3114,11 +3274,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 +3289,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 +3307,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 +3474,7 @@ namespace ts {
return concatenate(semanticDiagnostics, declarationDiagnostics);
}
- function getCompilerOptionsDiagnostics() {
+ function getProgramDiagnostics() {
synchronizeHostData();
return program.getOptionsDiagnostics(cancellationToken).concat(
program.getGlobalDiagnostics(cancellationToken));
@@ -8265,7 +8435,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/languageServiceExtensions/can-add-program-diagnostics.js b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-add-program-diagnostics.js
new file mode 100644
index 0000000000000..21b4721f1a6ed
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-add-program-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/languageServiceExtensions/can-chain-services.errors.txt b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-chain-services.errors.txt
new file mode 100644
index 0000000000000..5402510857df8
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-chain-services.errors.txt
@@ -0,0 +1,10 @@
+/hello-error.ts(1,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+/hello-error.ts(1,31): error TS1109: Expression expected.
+
+
+==== /hello-error.ts (2 errors) ====
+ console.log("Hello, error!") -
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+
+!!! error TS1109: Expression expected.
\ No newline at end of file
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-chain-services.js b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-chain-services.js
new file mode 100644
index 0000000000000..10802294f21ba
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-chain-services.js
@@ -0,0 +1,5 @@
+//// [hello-error.ts]
+console.log("Hello, error!") -
+
+//// [hello-error.js]
+console.log("Hello, error!") - ;
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-all-functions.errors.txt b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-all-functions.errors.txt
new file mode 100644
index 0000000000000..c268502890d90
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-all-functions.errors.txt
@@ -0,0 +1,653 @@
+/atotc.ts(1,1): error TS2304: Cannot find name 'It'.
+/atotc.ts(1,4): error TS1005: ';' expected.
+/atotc.ts(1,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(1,8): error TS1005: ';' expected.
+/atotc.ts(1,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(1,12): error TS1005: ';' expected.
+/atotc.ts(1,12): error TS2304: Cannot find name 'best'.
+/atotc.ts(1,17): error TS1005: ';' expected.
+/atotc.ts(1,17): error TS2304: Cannot find name 'of'.
+/atotc.ts(1,20): error TS1005: ';' expected.
+/atotc.ts(1,20): error TS2304: Cannot find name 'times'.
+/atotc.ts(1,27): error TS2304: Cannot find name 'it'.
+/atotc.ts(1,30): error TS1005: ';' expected.
+/atotc.ts(1,30): error TS2304: Cannot find name 'was'.
+/atotc.ts(1,34): error TS1005: ';' expected.
+/atotc.ts(1,34): error TS2304: Cannot find name 'the'.
+/atotc.ts(1,38): error TS1005: ';' expected.
+/atotc.ts(1,38): error TS2304: Cannot find name 'worst'.
+/atotc.ts(1,44): error TS1005: ';' expected.
+/atotc.ts(1,44): error TS2304: Cannot find name 'of'.
+/atotc.ts(1,47): error TS1005: ';' expected.
+/atotc.ts(1,47): error TS2304: Cannot find name 'times'.
+/atotc.ts(2,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(2,4): error TS1005: ';' expected.
+/atotc.ts(2,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(2,8): error TS1005: ';' expected.
+/atotc.ts(2,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(2,12): error TS1005: ';' expected.
+/atotc.ts(2,12): error TS2304: Cannot find name 'age'.
+/atotc.ts(2,16): error TS1005: ';' expected.
+/atotc.ts(2,16): error TS2304: Cannot find name 'of'.
+/atotc.ts(2,19): error TS1005: ';' expected.
+/atotc.ts(2,19): error TS2304: Cannot find name 'wisdom'.
+/atotc.ts(2,27): error TS2304: Cannot find name 'it'.
+/atotc.ts(2,30): error TS1005: ';' expected.
+/atotc.ts(2,30): error TS2304: Cannot find name 'was'.
+/atotc.ts(2,34): error TS1005: ';' expected.
+/atotc.ts(2,34): error TS2304: Cannot find name 'the'.
+/atotc.ts(2,38): error TS1005: ';' expected.
+/atotc.ts(2,38): error TS2304: Cannot find name 'age'.
+/atotc.ts(2,42): error TS1005: ';' expected.
+/atotc.ts(2,42): error TS2304: Cannot find name 'of'.
+/atotc.ts(2,45): error TS1005: ';' expected.
+/atotc.ts(2,45): error TS2304: Cannot find name 'foolishness'.
+/atotc.ts(3,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(3,4): error TS1005: ';' expected.
+/atotc.ts(3,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(3,8): error TS1005: ';' expected.
+/atotc.ts(3,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(3,12): error TS1005: ';' expected.
+/atotc.ts(3,12): error TS2304: Cannot find name 'epoch'.
+/atotc.ts(3,18): error TS1005: ';' expected.
+/atotc.ts(3,18): error TS2304: Cannot find name 'of'.
+/atotc.ts(3,21): error TS1005: ';' expected.
+/atotc.ts(3,21): error TS2304: Cannot find name 'belief'.
+/atotc.ts(3,29): error TS2304: Cannot find name 'it'.
+/atotc.ts(3,32): error TS1005: ';' expected.
+/atotc.ts(3,32): error TS2304: Cannot find name 'was'.
+/atotc.ts(3,36): error TS1005: ';' expected.
+/atotc.ts(3,36): error TS2304: Cannot find name 'the'.
+/atotc.ts(3,40): error TS1005: ';' expected.
+/atotc.ts(3,40): error TS2304: Cannot find name 'epoch'.
+/atotc.ts(3,46): error TS1005: ';' expected.
+/atotc.ts(3,46): error TS2304: Cannot find name 'of'.
+/atotc.ts(3,49): error TS1005: ';' expected.
+/atotc.ts(3,49): error TS2304: Cannot find name 'incredulity'.
+/atotc.ts(4,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(4,4): error TS1005: ';' expected.
+/atotc.ts(4,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(4,8): error TS1005: ';' expected.
+/atotc.ts(4,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(4,12): error TS1005: ';' expected.
+/atotc.ts(4,12): error TS2304: Cannot find name 'season'.
+/atotc.ts(4,19): error TS1005: ';' expected.
+/atotc.ts(4,19): error TS2304: Cannot find name 'of'.
+/atotc.ts(4,22): error TS1005: ';' expected.
+/atotc.ts(4,22): error TS2304: Cannot find name 'Light'.
+/atotc.ts(4,29): error TS2304: Cannot find name 'it'.
+/atotc.ts(4,32): error TS1005: ';' expected.
+/atotc.ts(4,32): error TS2304: Cannot find name 'was'.
+/atotc.ts(4,36): error TS1005: ';' expected.
+/atotc.ts(4,36): error TS2304: Cannot find name 'the'.
+/atotc.ts(4,40): error TS1005: ';' expected.
+/atotc.ts(4,40): error TS2304: Cannot find name 'season'.
+/atotc.ts(4,47): error TS1005: ';' expected.
+/atotc.ts(4,47): error TS2304: Cannot find name 'of'.
+/atotc.ts(4,50): error TS1005: ';' expected.
+/atotc.ts(4,50): error TS2304: Cannot find name 'Darkness'.
+/atotc.ts(5,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(5,4): error TS1005: ';' expected.
+/atotc.ts(5,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(5,8): error TS1005: ';' expected.
+/atotc.ts(5,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(5,12): error TS1005: ';' expected.
+/atotc.ts(5,12): error TS2304: Cannot find name 'spring'.
+/atotc.ts(5,19): error TS1005: ';' expected.
+/atotc.ts(5,19): error TS2304: Cannot find name 'of'.
+/atotc.ts(5,22): error TS1005: ';' expected.
+/atotc.ts(5,22): error TS2304: Cannot find name 'hope'.
+/atotc.ts(5,28): error TS2304: Cannot find name 'it'.
+/atotc.ts(5,31): error TS1005: ';' expected.
+/atotc.ts(5,31): error TS2304: Cannot find name 'was'.
+/atotc.ts(5,35): error TS1005: ';' expected.
+/atotc.ts(5,35): error TS2304: Cannot find name 'the'.
+/atotc.ts(5,39): error TS1005: ';' expected.
+/atotc.ts(5,39): error TS2304: Cannot find name 'winter'.
+/atotc.ts(5,46): error TS1005: ';' expected.
+/atotc.ts(5,46): error TS2304: Cannot find name 'of'.
+/atotc.ts(5,49): error TS1005: ';' expected.
+/atotc.ts(5,49): error TS2304: Cannot find name 'despair'.
+/atotc.ts(6,1): error TS2304: Cannot find name 'we'.
+/atotc.ts(6,4): error TS1005: ';' expected.
+/atotc.ts(6,4): error TS2304: Cannot find name 'had'.
+/atotc.ts(6,8): error TS1005: ';' expected.
+/atotc.ts(6,8): error TS2304: Cannot find name 'everything'.
+/atotc.ts(6,19): error TS1005: ';' expected.
+/atotc.ts(6,19): error TS2304: Cannot find name 'before'.
+/atotc.ts(6,26): error TS1005: ';' expected.
+/atotc.ts(6,26): error TS2304: Cannot find name 'us'.
+/atotc.ts(6,30): error TS2304: Cannot find name 'we'.
+/atotc.ts(6,33): error TS1005: ';' expected.
+/atotc.ts(6,33): error TS2304: Cannot find name 'had'.
+/atotc.ts(6,37): error TS1005: ';' expected.
+/atotc.ts(6,37): error TS2304: Cannot find name 'nothing'.
+/atotc.ts(6,45): error TS1005: ';' expected.
+/atotc.ts(6,45): error TS2304: Cannot find name 'before'.
+/atotc.ts(6,52): error TS1005: ';' expected.
+/atotc.ts(6,52): error TS2304: Cannot find name 'us'.
+/atotc.ts(7,1): error TS2304: Cannot find name 'we'.
+/atotc.ts(7,4): error TS1005: ';' expected.
+/atotc.ts(7,4): error TS2304: Cannot find name 'were'.
+/atotc.ts(7,9): error TS1005: ';' expected.
+/atotc.ts(7,9): error TS2304: Cannot find name 'all'.
+/atotc.ts(7,13): error TS1005: ';' expected.
+/atotc.ts(7,13): error TS2304: Cannot find name 'going'.
+/atotc.ts(7,19): error TS1005: ';' expected.
+/atotc.ts(7,19): error TS2304: Cannot find name 'direct'.
+/atotc.ts(7,26): error TS1005: ';' expected.
+/atotc.ts(7,26): error TS2304: Cannot find name 'to'.
+/atotc.ts(7,29): error TS1005: ';' expected.
+/atotc.ts(7,29): error TS2304: Cannot find name 'Heaven'.
+/atotc.ts(7,37): error TS2304: Cannot find name 'we'.
+/atotc.ts(7,40): error TS1005: ';' expected.
+/atotc.ts(7,40): error TS2304: Cannot find name 'were'.
+/atotc.ts(7,45): error TS1005: ';' expected.
+/atotc.ts(7,45): error TS2304: Cannot find name 'all'.
+/atotc.ts(7,49): error TS1005: ';' expected.
+/atotc.ts(7,49): error TS2304: Cannot find name 'going'.
+/atotc.ts(7,55): error TS1005: ';' expected.
+/atotc.ts(7,55): error TS2304: Cannot find name 'direct'.
+/atotc.ts(8,1): error TS2304: Cannot find name 'the'.
+/atotc.ts(8,5): error TS1005: ';' expected.
+/atotc.ts(8,5): error TS2304: Cannot find name 'other'.
+/atotc.ts(8,11): error TS1005: ';' expected.
+/atotc.ts(8,11): error TS2304: Cannot find name 'way'.
+/atotc.ts(8,19): error TS2304: Cannot find name 'short'.
+/atotc.ts(8,26): error TS2304: Cannot find name 'the'.
+/atotc.ts(8,30): error TS1005: ';' expected.
+/atotc.ts(8,30): error TS2304: Cannot find name 'period'.
+/atotc.ts(8,37): error TS1005: ';' expected.
+/atotc.ts(8,37): error TS2304: Cannot find name 'was'.
+/atotc.ts(8,41): error TS1005: ';' expected.
+/atotc.ts(8,41): error TS2304: Cannot find name 'so'.
+/atotc.ts(8,44): error TS1005: ';' expected.
+/atotc.ts(8,44): error TS2304: Cannot find name 'far'.
+/atotc.ts(8,48): error TS1005: ';' expected.
+/atotc.ts(8,48): error TS2304: Cannot find name 'like'.
+/atotc.ts(8,53): error TS1005: ';' expected.
+/atotc.ts(8,53): error TS2304: Cannot find name 'the'.
+/atotc.ts(8,57): error TS1005: ';' expected.
+/atotc.ts(8,57): error TS2304: Cannot find name 'present'.
+/atotc.ts(9,1): error TS2304: Cannot find name 'period'.
+/atotc.ts(9,9): error TS2304: Cannot find name 'that'.
+/atotc.ts(9,14): error TS1005: ';' expected.
+/atotc.ts(9,14): error TS2304: Cannot find name 'some'.
+/atotc.ts(9,19): error TS1005: ';' expected.
+/atotc.ts(9,19): error TS2304: Cannot find name 'of'.
+/atotc.ts(9,22): error TS1005: ';' expected.
+/atotc.ts(9,22): error TS2304: Cannot find name 'its'.
+/atotc.ts(9,26): error TS1005: ';' expected.
+/atotc.ts(9,26): error TS2304: Cannot find name 'noisiest'.
+/atotc.ts(9,35): error TS1005: ';' expected.
+/atotc.ts(9,35): error TS2304: Cannot find name 'authorities'.
+/atotc.ts(9,47): error TS1005: ';' expected.
+/atotc.ts(9,47): error TS2304: Cannot find name 'insisted'.
+/atotc.ts(9,56): error TS1005: ';' expected.
+/atotc.ts(9,56): error TS2304: Cannot find name 'on'.
+/atotc.ts(9,59): error TS1005: ';' expected.
+/atotc.ts(9,59): error TS2304: Cannot find name 'its'.
+/atotc.ts(10,1): error TS2304: Cannot find name 'being'.
+/atotc.ts(10,7): error TS1005: ';' expected.
+/atotc.ts(10,7): error TS2304: Cannot find name 'received'.
+/atotc.ts(10,17): error TS1109: Expression expected.
+/atotc.ts(10,21): error TS1005: '(' expected.
+/atotc.ts(10,21): error TS2304: Cannot find name 'good'.
+/atotc.ts(10,26): error TS1005: ';' expected.
+/atotc.ts(10,26): error TS2304: Cannot find name 'or'.
+/atotc.ts(10,29): error TS1005: ';' expected.
+/atotc.ts(10,33): error TS1005: '(' expected.
+/atotc.ts(10,33): error TS2304: Cannot find name 'evil'.
+/atotc.ts(10,33): error TS2406: Invalid left-hand side in 'for...in' statement.
+/atotc.ts(10,39): error TS1109: Expression expected.
+/atotc.ts(10,42): error TS2304: Cannot find name 'the'.
+/atotc.ts(10,46): error TS1005: ')' expected.
+/atotc.ts(10,46): error TS2304: Cannot find name 'superlative'.
+/atotc.ts(10,58): error TS1005: ';' expected.
+/atotc.ts(10,58): error TS2304: Cannot find name 'degree'.
+/atotc.ts(11,1): error TS2304: Cannot find name 'of'.
+/atotc.ts(11,4): error TS1005: ';' expected.
+/atotc.ts(11,4): error TS2304: Cannot find name 'comparison'.
+/atotc.ts(11,15): error TS1005: ';' expected.
+/atotc.ts(11,15): error TS2304: Cannot find name 'only'.
+/atotc.ts(11,20): error TS1003: Identifier expected.
+
+
+==== /atotc.ts (213 errors) ====
+ It was the best of times, it was the worst of times,
+ ~~
+!!! error TS2304: Cannot find name 'It'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'best'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'times'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'worst'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'times'.
+ it was the age of wisdom, it was the age of foolishness,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'age'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'wisdom'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'age'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'foolishness'.
+ it was the epoch of belief, it was the epoch of incredulity,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'epoch'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'belief'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'epoch'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'incredulity'.
+ it was the season of Light, it was the season of Darkness,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'season'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'Light'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'season'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'Darkness'.
+ it was the spring of hope, it was the winter of despair,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'spring'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'hope'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'winter'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~
+!!! error TS2304: Cannot find name 'despair'.
+ we had everything before us, we had nothing before us,
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'had'.
+ ~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~
+!!! error TS2304: Cannot find name 'everything'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'before'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'us'.
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'had'.
+ ~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~
+!!! error TS2304: Cannot find name 'nothing'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'before'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'us'.
+ we were all going direct to Heaven, we were all going direct
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'were'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'all'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'going'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'direct'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'to'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'Heaven'.
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'were'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'all'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'going'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'direct'.
+ the other way--in short, the period was so far like the present
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'other'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'way'.
+ ~~~~~
+!!! error TS2304: Cannot find name 'short'.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'period'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'so'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'far'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'like'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~
+!!! error TS2304: Cannot find name 'present'.
+ period, that some of its noisiest authorities insisted on its
+ ~~~~~~
+!!! error TS2304: Cannot find name 'period'.
+ ~~~~
+!!! error TS2304: Cannot find name 'that'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'some'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'its'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'noisiest'.
+ ~~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'authorities'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'insisted'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'on'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'its'.
+ being received, for good or for evil, in the superlative degree
+ ~~~~~
+!!! error TS2304: Cannot find name 'being'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'received'.
+ ~~~
+!!! error TS1109: Expression expected.
+ ~~~~
+!!! error TS1005: '(' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'good'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'or'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS1005: '(' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'evil'.
+ ~~~~~
+!!! error TS2406: Invalid left-hand side in 'for...in' statement.
+ ~~
+!!! error TS1109: Expression expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~~~~~~
+!!! error TS1005: ')' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'superlative'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'degree'.
+ of comparison only.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~
+!!! error TS2304: Cannot find name 'comparison'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'only'.
+
+!!! error TS1003: Identifier expected.
\ No newline at end of file
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-all-functions.js b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-all-functions.js
new file mode 100644
index 0000000000000..30c1de3575183
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-all-functions.js
@@ -0,0 +1,117 @@
+//// [atotc.ts]
+It was the best of times, it was the worst of times,
+it was the age of wisdom, it was the age of foolishness,
+it was the epoch of belief, it was the epoch of incredulity,
+it was the season of Light, it was the season of Darkness,
+it was the spring of hope, it was the winter of despair,
+we had everything before us, we had nothing before us,
+we were all going direct to Heaven, we were all going direct
+the other way--in short, the period was so far like the present
+period, that some of its noisiest authorities insisted on its
+being received, for good or for evil, in the superlative degree
+of comparison only.
+
+//// [atotc.js]
+It;
+was;
+the;
+best;
+of;
+times, it;
+was;
+the;
+worst;
+of;
+times,
+ it;
+was;
+the;
+age;
+of;
+wisdom, it;
+was;
+the;
+age;
+of;
+foolishness,
+ it;
+was;
+the;
+epoch;
+of;
+belief, it;
+was;
+the;
+epoch;
+of;
+incredulity,
+ it;
+was;
+the;
+season;
+of;
+Light, it;
+was;
+the;
+season;
+of;
+Darkness,
+ it;
+was;
+the;
+spring;
+of;
+hope, it;
+was;
+the;
+winter;
+of;
+despair,
+ we;
+had;
+everything;
+before;
+us, we;
+had;
+nothing;
+before;
+us,
+ we;
+were;
+all;
+going;
+direct;
+to;
+Heaven, we;
+were;
+all;
+going;
+direct;
+the;
+other;
+way-- in short, the;
+period;
+was;
+so;
+far;
+like;
+the;
+present;
+period, that;
+some;
+of;
+its;
+noisiest;
+authorities;
+insisted;
+on;
+its;
+being;
+received, ;
+for (good; or; )
+ for (evil, in the)
+ superlative;
+degree;
+of;
+comparison;
+only.;
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-passthru.errors.txt b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-passthru.errors.txt
new file mode 100644
index 0000000000000..5402510857df8
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-passthru.errors.txt
@@ -0,0 +1,10 @@
+/hello-error.ts(1,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+/hello-error.ts(1,31): error TS1109: Expression expected.
+
+
+==== /hello-error.ts (2 errors) ====
+ console.log("Hello, error!") -
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+
+!!! error TS1109: Expression expected.
\ No newline at end of file
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-passthru.js b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-passthru.js
new file mode 100644
index 0000000000000..10802294f21ba
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-filter-passthru.js
@@ -0,0 +1,5 @@
+//// [hello-error.ts]
+console.log("Hello, error!") -
+
+//// [hello-error.js]
+console.log("Hello, error!") - ;
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-override-all-functions.errors.txt b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-override-all-functions.errors.txt
new file mode 100644
index 0000000000000..c268502890d90
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-override-all-functions.errors.txt
@@ -0,0 +1,653 @@
+/atotc.ts(1,1): error TS2304: Cannot find name 'It'.
+/atotc.ts(1,4): error TS1005: ';' expected.
+/atotc.ts(1,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(1,8): error TS1005: ';' expected.
+/atotc.ts(1,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(1,12): error TS1005: ';' expected.
+/atotc.ts(1,12): error TS2304: Cannot find name 'best'.
+/atotc.ts(1,17): error TS1005: ';' expected.
+/atotc.ts(1,17): error TS2304: Cannot find name 'of'.
+/atotc.ts(1,20): error TS1005: ';' expected.
+/atotc.ts(1,20): error TS2304: Cannot find name 'times'.
+/atotc.ts(1,27): error TS2304: Cannot find name 'it'.
+/atotc.ts(1,30): error TS1005: ';' expected.
+/atotc.ts(1,30): error TS2304: Cannot find name 'was'.
+/atotc.ts(1,34): error TS1005: ';' expected.
+/atotc.ts(1,34): error TS2304: Cannot find name 'the'.
+/atotc.ts(1,38): error TS1005: ';' expected.
+/atotc.ts(1,38): error TS2304: Cannot find name 'worst'.
+/atotc.ts(1,44): error TS1005: ';' expected.
+/atotc.ts(1,44): error TS2304: Cannot find name 'of'.
+/atotc.ts(1,47): error TS1005: ';' expected.
+/atotc.ts(1,47): error TS2304: Cannot find name 'times'.
+/atotc.ts(2,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(2,4): error TS1005: ';' expected.
+/atotc.ts(2,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(2,8): error TS1005: ';' expected.
+/atotc.ts(2,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(2,12): error TS1005: ';' expected.
+/atotc.ts(2,12): error TS2304: Cannot find name 'age'.
+/atotc.ts(2,16): error TS1005: ';' expected.
+/atotc.ts(2,16): error TS2304: Cannot find name 'of'.
+/atotc.ts(2,19): error TS1005: ';' expected.
+/atotc.ts(2,19): error TS2304: Cannot find name 'wisdom'.
+/atotc.ts(2,27): error TS2304: Cannot find name 'it'.
+/atotc.ts(2,30): error TS1005: ';' expected.
+/atotc.ts(2,30): error TS2304: Cannot find name 'was'.
+/atotc.ts(2,34): error TS1005: ';' expected.
+/atotc.ts(2,34): error TS2304: Cannot find name 'the'.
+/atotc.ts(2,38): error TS1005: ';' expected.
+/atotc.ts(2,38): error TS2304: Cannot find name 'age'.
+/atotc.ts(2,42): error TS1005: ';' expected.
+/atotc.ts(2,42): error TS2304: Cannot find name 'of'.
+/atotc.ts(2,45): error TS1005: ';' expected.
+/atotc.ts(2,45): error TS2304: Cannot find name 'foolishness'.
+/atotc.ts(3,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(3,4): error TS1005: ';' expected.
+/atotc.ts(3,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(3,8): error TS1005: ';' expected.
+/atotc.ts(3,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(3,12): error TS1005: ';' expected.
+/atotc.ts(3,12): error TS2304: Cannot find name 'epoch'.
+/atotc.ts(3,18): error TS1005: ';' expected.
+/atotc.ts(3,18): error TS2304: Cannot find name 'of'.
+/atotc.ts(3,21): error TS1005: ';' expected.
+/atotc.ts(3,21): error TS2304: Cannot find name 'belief'.
+/atotc.ts(3,29): error TS2304: Cannot find name 'it'.
+/atotc.ts(3,32): error TS1005: ';' expected.
+/atotc.ts(3,32): error TS2304: Cannot find name 'was'.
+/atotc.ts(3,36): error TS1005: ';' expected.
+/atotc.ts(3,36): error TS2304: Cannot find name 'the'.
+/atotc.ts(3,40): error TS1005: ';' expected.
+/atotc.ts(3,40): error TS2304: Cannot find name 'epoch'.
+/atotc.ts(3,46): error TS1005: ';' expected.
+/atotc.ts(3,46): error TS2304: Cannot find name 'of'.
+/atotc.ts(3,49): error TS1005: ';' expected.
+/atotc.ts(3,49): error TS2304: Cannot find name 'incredulity'.
+/atotc.ts(4,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(4,4): error TS1005: ';' expected.
+/atotc.ts(4,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(4,8): error TS1005: ';' expected.
+/atotc.ts(4,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(4,12): error TS1005: ';' expected.
+/atotc.ts(4,12): error TS2304: Cannot find name 'season'.
+/atotc.ts(4,19): error TS1005: ';' expected.
+/atotc.ts(4,19): error TS2304: Cannot find name 'of'.
+/atotc.ts(4,22): error TS1005: ';' expected.
+/atotc.ts(4,22): error TS2304: Cannot find name 'Light'.
+/atotc.ts(4,29): error TS2304: Cannot find name 'it'.
+/atotc.ts(4,32): error TS1005: ';' expected.
+/atotc.ts(4,32): error TS2304: Cannot find name 'was'.
+/atotc.ts(4,36): error TS1005: ';' expected.
+/atotc.ts(4,36): error TS2304: Cannot find name 'the'.
+/atotc.ts(4,40): error TS1005: ';' expected.
+/atotc.ts(4,40): error TS2304: Cannot find name 'season'.
+/atotc.ts(4,47): error TS1005: ';' expected.
+/atotc.ts(4,47): error TS2304: Cannot find name 'of'.
+/atotc.ts(4,50): error TS1005: ';' expected.
+/atotc.ts(4,50): error TS2304: Cannot find name 'Darkness'.
+/atotc.ts(5,1): error TS2304: Cannot find name 'it'.
+/atotc.ts(5,4): error TS1005: ';' expected.
+/atotc.ts(5,4): error TS2304: Cannot find name 'was'.
+/atotc.ts(5,8): error TS1005: ';' expected.
+/atotc.ts(5,8): error TS2304: Cannot find name 'the'.
+/atotc.ts(5,12): error TS1005: ';' expected.
+/atotc.ts(5,12): error TS2304: Cannot find name 'spring'.
+/atotc.ts(5,19): error TS1005: ';' expected.
+/atotc.ts(5,19): error TS2304: Cannot find name 'of'.
+/atotc.ts(5,22): error TS1005: ';' expected.
+/atotc.ts(5,22): error TS2304: Cannot find name 'hope'.
+/atotc.ts(5,28): error TS2304: Cannot find name 'it'.
+/atotc.ts(5,31): error TS1005: ';' expected.
+/atotc.ts(5,31): error TS2304: Cannot find name 'was'.
+/atotc.ts(5,35): error TS1005: ';' expected.
+/atotc.ts(5,35): error TS2304: Cannot find name 'the'.
+/atotc.ts(5,39): error TS1005: ';' expected.
+/atotc.ts(5,39): error TS2304: Cannot find name 'winter'.
+/atotc.ts(5,46): error TS1005: ';' expected.
+/atotc.ts(5,46): error TS2304: Cannot find name 'of'.
+/atotc.ts(5,49): error TS1005: ';' expected.
+/atotc.ts(5,49): error TS2304: Cannot find name 'despair'.
+/atotc.ts(6,1): error TS2304: Cannot find name 'we'.
+/atotc.ts(6,4): error TS1005: ';' expected.
+/atotc.ts(6,4): error TS2304: Cannot find name 'had'.
+/atotc.ts(6,8): error TS1005: ';' expected.
+/atotc.ts(6,8): error TS2304: Cannot find name 'everything'.
+/atotc.ts(6,19): error TS1005: ';' expected.
+/atotc.ts(6,19): error TS2304: Cannot find name 'before'.
+/atotc.ts(6,26): error TS1005: ';' expected.
+/atotc.ts(6,26): error TS2304: Cannot find name 'us'.
+/atotc.ts(6,30): error TS2304: Cannot find name 'we'.
+/atotc.ts(6,33): error TS1005: ';' expected.
+/atotc.ts(6,33): error TS2304: Cannot find name 'had'.
+/atotc.ts(6,37): error TS1005: ';' expected.
+/atotc.ts(6,37): error TS2304: Cannot find name 'nothing'.
+/atotc.ts(6,45): error TS1005: ';' expected.
+/atotc.ts(6,45): error TS2304: Cannot find name 'before'.
+/atotc.ts(6,52): error TS1005: ';' expected.
+/atotc.ts(6,52): error TS2304: Cannot find name 'us'.
+/atotc.ts(7,1): error TS2304: Cannot find name 'we'.
+/atotc.ts(7,4): error TS1005: ';' expected.
+/atotc.ts(7,4): error TS2304: Cannot find name 'were'.
+/atotc.ts(7,9): error TS1005: ';' expected.
+/atotc.ts(7,9): error TS2304: Cannot find name 'all'.
+/atotc.ts(7,13): error TS1005: ';' expected.
+/atotc.ts(7,13): error TS2304: Cannot find name 'going'.
+/atotc.ts(7,19): error TS1005: ';' expected.
+/atotc.ts(7,19): error TS2304: Cannot find name 'direct'.
+/atotc.ts(7,26): error TS1005: ';' expected.
+/atotc.ts(7,26): error TS2304: Cannot find name 'to'.
+/atotc.ts(7,29): error TS1005: ';' expected.
+/atotc.ts(7,29): error TS2304: Cannot find name 'Heaven'.
+/atotc.ts(7,37): error TS2304: Cannot find name 'we'.
+/atotc.ts(7,40): error TS1005: ';' expected.
+/atotc.ts(7,40): error TS2304: Cannot find name 'were'.
+/atotc.ts(7,45): error TS1005: ';' expected.
+/atotc.ts(7,45): error TS2304: Cannot find name 'all'.
+/atotc.ts(7,49): error TS1005: ';' expected.
+/atotc.ts(7,49): error TS2304: Cannot find name 'going'.
+/atotc.ts(7,55): error TS1005: ';' expected.
+/atotc.ts(7,55): error TS2304: Cannot find name 'direct'.
+/atotc.ts(8,1): error TS2304: Cannot find name 'the'.
+/atotc.ts(8,5): error TS1005: ';' expected.
+/atotc.ts(8,5): error TS2304: Cannot find name 'other'.
+/atotc.ts(8,11): error TS1005: ';' expected.
+/atotc.ts(8,11): error TS2304: Cannot find name 'way'.
+/atotc.ts(8,19): error TS2304: Cannot find name 'short'.
+/atotc.ts(8,26): error TS2304: Cannot find name 'the'.
+/atotc.ts(8,30): error TS1005: ';' expected.
+/atotc.ts(8,30): error TS2304: Cannot find name 'period'.
+/atotc.ts(8,37): error TS1005: ';' expected.
+/atotc.ts(8,37): error TS2304: Cannot find name 'was'.
+/atotc.ts(8,41): error TS1005: ';' expected.
+/atotc.ts(8,41): error TS2304: Cannot find name 'so'.
+/atotc.ts(8,44): error TS1005: ';' expected.
+/atotc.ts(8,44): error TS2304: Cannot find name 'far'.
+/atotc.ts(8,48): error TS1005: ';' expected.
+/atotc.ts(8,48): error TS2304: Cannot find name 'like'.
+/atotc.ts(8,53): error TS1005: ';' expected.
+/atotc.ts(8,53): error TS2304: Cannot find name 'the'.
+/atotc.ts(8,57): error TS1005: ';' expected.
+/atotc.ts(8,57): error TS2304: Cannot find name 'present'.
+/atotc.ts(9,1): error TS2304: Cannot find name 'period'.
+/atotc.ts(9,9): error TS2304: Cannot find name 'that'.
+/atotc.ts(9,14): error TS1005: ';' expected.
+/atotc.ts(9,14): error TS2304: Cannot find name 'some'.
+/atotc.ts(9,19): error TS1005: ';' expected.
+/atotc.ts(9,19): error TS2304: Cannot find name 'of'.
+/atotc.ts(9,22): error TS1005: ';' expected.
+/atotc.ts(9,22): error TS2304: Cannot find name 'its'.
+/atotc.ts(9,26): error TS1005: ';' expected.
+/atotc.ts(9,26): error TS2304: Cannot find name 'noisiest'.
+/atotc.ts(9,35): error TS1005: ';' expected.
+/atotc.ts(9,35): error TS2304: Cannot find name 'authorities'.
+/atotc.ts(9,47): error TS1005: ';' expected.
+/atotc.ts(9,47): error TS2304: Cannot find name 'insisted'.
+/atotc.ts(9,56): error TS1005: ';' expected.
+/atotc.ts(9,56): error TS2304: Cannot find name 'on'.
+/atotc.ts(9,59): error TS1005: ';' expected.
+/atotc.ts(9,59): error TS2304: Cannot find name 'its'.
+/atotc.ts(10,1): error TS2304: Cannot find name 'being'.
+/atotc.ts(10,7): error TS1005: ';' expected.
+/atotc.ts(10,7): error TS2304: Cannot find name 'received'.
+/atotc.ts(10,17): error TS1109: Expression expected.
+/atotc.ts(10,21): error TS1005: '(' expected.
+/atotc.ts(10,21): error TS2304: Cannot find name 'good'.
+/atotc.ts(10,26): error TS1005: ';' expected.
+/atotc.ts(10,26): error TS2304: Cannot find name 'or'.
+/atotc.ts(10,29): error TS1005: ';' expected.
+/atotc.ts(10,33): error TS1005: '(' expected.
+/atotc.ts(10,33): error TS2304: Cannot find name 'evil'.
+/atotc.ts(10,33): error TS2406: Invalid left-hand side in 'for...in' statement.
+/atotc.ts(10,39): error TS1109: Expression expected.
+/atotc.ts(10,42): error TS2304: Cannot find name 'the'.
+/atotc.ts(10,46): error TS1005: ')' expected.
+/atotc.ts(10,46): error TS2304: Cannot find name 'superlative'.
+/atotc.ts(10,58): error TS1005: ';' expected.
+/atotc.ts(10,58): error TS2304: Cannot find name 'degree'.
+/atotc.ts(11,1): error TS2304: Cannot find name 'of'.
+/atotc.ts(11,4): error TS1005: ';' expected.
+/atotc.ts(11,4): error TS2304: Cannot find name 'comparison'.
+/atotc.ts(11,15): error TS1005: ';' expected.
+/atotc.ts(11,15): error TS2304: Cannot find name 'only'.
+/atotc.ts(11,20): error TS1003: Identifier expected.
+
+
+==== /atotc.ts (213 errors) ====
+ It was the best of times, it was the worst of times,
+ ~~
+!!! error TS2304: Cannot find name 'It'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'best'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'times'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'worst'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'times'.
+ it was the age of wisdom, it was the age of foolishness,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'age'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'wisdom'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'age'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'foolishness'.
+ it was the epoch of belief, it was the epoch of incredulity,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'epoch'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'belief'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'epoch'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'incredulity'.
+ it was the season of Light, it was the season of Darkness,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'season'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'Light'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'season'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'Darkness'.
+ it was the spring of hope, it was the winter of despair,
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'spring'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'hope'.
+ ~~
+!!! error TS2304: Cannot find name 'it'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'winter'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~
+!!! error TS2304: Cannot find name 'despair'.
+ we had everything before us, we had nothing before us,
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'had'.
+ ~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~
+!!! error TS2304: Cannot find name 'everything'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'before'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'us'.
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'had'.
+ ~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~
+!!! error TS2304: Cannot find name 'nothing'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'before'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'us'.
+ we were all going direct to Heaven, we were all going direct
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'were'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'all'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'going'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'direct'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'to'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'Heaven'.
+ ~~
+!!! error TS2304: Cannot find name 'we'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'were'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'all'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'going'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'direct'.
+ the other way--in short, the period was so far like the present
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~
+!!! error TS2304: Cannot find name 'other'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'way'.
+ ~~~~~
+!!! error TS2304: Cannot find name 'short'.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'period'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'was'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'so'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'far'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'like'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~
+!!! error TS2304: Cannot find name 'present'.
+ period, that some of its noisiest authorities insisted on its
+ ~~~~~~
+!!! error TS2304: Cannot find name 'period'.
+ ~~~~
+!!! error TS2304: Cannot find name 'that'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'some'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'its'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'noisiest'.
+ ~~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'authorities'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'insisted'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'on'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~
+!!! error TS2304: Cannot find name 'its'.
+ being received, for good or for evil, in the superlative degree
+ ~~~~~
+!!! error TS2304: Cannot find name 'being'.
+ ~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~
+!!! error TS2304: Cannot find name 'received'.
+ ~~~
+!!! error TS1109: Expression expected.
+ ~~~~
+!!! error TS1005: '(' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'good'.
+ ~~
+!!! error TS1005: ';' expected.
+ ~~
+!!! error TS2304: Cannot find name 'or'.
+ ~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS1005: '(' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'evil'.
+ ~~~~~
+!!! error TS2406: Invalid left-hand side in 'for...in' statement.
+ ~~
+!!! error TS1109: Expression expected.
+ ~~~
+!!! error TS2304: Cannot find name 'the'.
+ ~~~~~~~~~~~
+!!! error TS1005: ')' expected.
+ ~~~~~~~~~~~
+!!! error TS2304: Cannot find name 'superlative'.
+ ~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~
+!!! error TS2304: Cannot find name 'degree'.
+ of comparison only.
+ ~~
+!!! error TS2304: Cannot find name 'of'.
+ ~~~~~~~~~~
+!!! error TS1005: ';' expected.
+ ~~~~~~~~~~
+!!! error TS2304: Cannot find name 'comparison'.
+ ~~~~
+!!! error TS1005: ';' expected.
+ ~~~~
+!!! error TS2304: Cannot find name 'only'.
+
+!!! error TS1003: Identifier expected.
\ No newline at end of file
diff --git a/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-override-all-functions.js b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-override-all-functions.js
new file mode 100644
index 0000000000000..30c1de3575183
--- /dev/null
+++ b/tests/baselines/reference/CompilerHost/languageServiceExtensions/can-override-all-functions.js
@@ -0,0 +1,117 @@
+//// [atotc.ts]
+It was the best of times, it was the worst of times,
+it was the age of wisdom, it was the age of foolishness,
+it was the epoch of belief, it was the epoch of incredulity,
+it was the season of Light, it was the season of Darkness,
+it was the spring of hope, it was the winter of despair,
+we had everything before us, we had nothing before us,
+we were all going direct to Heaven, we were all going direct
+the other way--in short, the period was so far like the present
+period, that some of its noisiest authorities insisted on its
+being received, for good or for evil, in the superlative degree
+of comparison only.
+
+//// [atotc.js]
+It;
+was;
+the;
+best;
+of;
+times, it;
+was;
+the;
+worst;
+of;
+times,
+ it;
+was;
+the;
+age;
+of;
+wisdom, it;
+was;
+the;
+age;
+of;
+foolishness,
+ it;
+was;
+the;
+epoch;
+of;
+belief, it;
+was;
+the;
+epoch;
+of;
+incredulity,
+ it;
+was;
+the;
+season;
+of;
+Light, it;
+was;
+the;
+season;
+of;
+Darkness,
+ it;
+was;
+the;
+spring;
+of;
+hope, it;
+was;
+the;
+winter;
+of;
+despair,
+ we;
+had;
+everything;
+before;
+us, we;
+had;
+nothing;
+before;
+us,
+ we;
+were;
+all;
+going;
+direct;
+to;
+Heaven, we;
+were;
+all;
+going;
+direct;
+the;
+other;
+way-- in short, the;
+period;
+was;
+so;
+far;
+like;
+the;
+present;
+period, that;
+some;
+of;
+its;
+noisiest;
+authorities;
+insisted;
+on;
+its;
+being;
+received, ;
+for (good; or; )
+ for (evil, in the)
+ superlative;
+degree;
+of;
+comparison;
+only.;
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/languageServiceExtensions/can-add-program-diagnostics.errors.txt b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-add-program-diagnostics.errors.txt
new file mode 100644
index 0000000000000..86ceab8437d30
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-add-program-diagnostics.errors.txt
@@ -0,0 +1,6 @@
+message test-plugin-loaded: Test language service plugin loaded!
+
+
+!!! message test-plugin-loaded: Test language service plugin loaded!
+==== /hello.ts (0 errors) ====
+ console.log("Hello, world!");/*EOL*/
\ No newline at end of file
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-add-program-diagnostics.js b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-add-program-diagnostics.js
new file mode 100644
index 0000000000000..b9f140e37bdde
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-add-program-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/languageServiceExtensions/can-chain-services.errors.txt b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-chain-services.errors.txt
new file mode 100644
index 0000000000000..28ac94a0b59fb
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-chain-services.errors.txt
@@ -0,0 +1,16 @@
+message program-diagnostics-mutated: Program diagnostics mutated!
+message semantic-diagnostics-mutated: Semantic diagnostics mutated!
+message syntactic-diagnostics-mutated: Syntactic diagnostics mutated!
+/hello-error.ts(1,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+/hello-error.ts(1,31): error TS1109: Expression expected.
+
+
+!!! message program-diagnostics-mutated: Program diagnostics mutated!
+!!! message semantic-diagnostics-mutated: Semantic diagnostics mutated!
+!!! message syntactic-diagnostics-mutated: Syntactic diagnostics mutated!
+==== /hello-error.ts (2 errors) ====
+ console.log("Hello, error!") -
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+
+!!! error TS1109: Expression expected.
\ No newline at end of file
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-chain-services.js b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-chain-services.js
new file mode 100644
index 0000000000000..9650d50b1bb7c
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-chain-services.js
@@ -0,0 +1,5 @@
+//// [hello-error.ts]
+console.log("Hello, error!") -
+
+//// [hello-error.js]
+console.log("Hello, error!") - ;
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-all-functions.errors.txt b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-all-functions.errors.txt
new file mode 100644
index 0000000000000..63a20e9cb70e1
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-all-functions.errors.txt
@@ -0,0 +1,20 @@
+message program-diagnostics-replaced: Program diagnostics replaced!
+message semantic-diagnostics-replaced: Semantic diagnostics replaced!
+message syntactic-diagnostics-replaced: Syntactic diagnostics replaced!
+
+
+!!! message program-diagnostics-replaced: Program diagnostics replaced!
+!!! message semantic-diagnostics-replaced: Semantic diagnostics replaced!
+!!! message syntactic-diagnostics-replaced: Syntactic diagnostics replaced!
+==== /atotc.ts (0 errors) ====
+ It was the best of times, it was the worst of times,
+ it was the age of wisdom, it was the age of foolishness,
+ it was the epoch of belief, it was the epoch of incredulity,
+ it was the season of Light, it was the season of Darkness,
+ it was the spring of hope, it was the winter of despair,
+ we had everything before us, we had nothing before us,
+ we were all going direct to Heaven, we were all going direct
+ the other way--in short, the period was so far like the present
+ period, that some of its noisiest authorities insisted on its
+ being received, for good or for evil, in the superlative degree
+ of comparison only.
\ No newline at end of file
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-all-functions.js b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-all-functions.js
new file mode 100644
index 0000000000000..0a4b5fe31af18
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-all-functions.js
@@ -0,0 +1,117 @@
+//// [atotc.ts]
+It was the best of times, it was the worst of times,
+it was the age of wisdom, it was the age of foolishness,
+it was the epoch of belief, it was the epoch of incredulity,
+it was the season of Light, it was the season of Darkness,
+it was the spring of hope, it was the winter of despair,
+we had everything before us, we had nothing before us,
+we were all going direct to Heaven, we were all going direct
+the other way--in short, the period was so far like the present
+period, that some of its noisiest authorities insisted on its
+being received, for good or for evil, in the superlative degree
+of comparison only.
+
+//// [atotc.js]
+It;
+was;
+the;
+best;
+of;
+times, it;
+was;
+the;
+worst;
+of;
+times,
+ it;
+was;
+the;
+age;
+of;
+wisdom, it;
+was;
+the;
+age;
+of;
+foolishness,
+ it;
+was;
+the;
+epoch;
+of;
+belief, it;
+was;
+the;
+epoch;
+of;
+incredulity,
+ it;
+was;
+the;
+season;
+of;
+Light, it;
+was;
+the;
+season;
+of;
+Darkness,
+ it;
+was;
+the;
+spring;
+of;
+hope, it;
+was;
+the;
+winter;
+of;
+despair,
+ we;
+had;
+everything;
+before;
+us, we;
+had;
+nothing;
+before;
+us,
+ we;
+were;
+all;
+going;
+direct;
+to;
+Heaven, we;
+were;
+all;
+going;
+direct;
+the;
+other;
+way-- in short, the;
+period;
+was;
+so;
+far;
+like;
+the;
+present;
+period, that;
+some;
+of;
+its;
+noisiest;
+authorities;
+insisted;
+on;
+its;
+being;
+received, ;
+for (good; or; )
+ for (evil, in the)
+ superlative;
+degree;
+of;
+comparison;
+only.;
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-passthru.errors.txt b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-passthru.errors.txt
new file mode 100644
index 0000000000000..5402510857df8
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-passthru.errors.txt
@@ -0,0 +1,10 @@
+/hello-error.ts(1,1): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+/hello-error.ts(1,31): error TS1109: Expression expected.
+
+
+==== /hello-error.ts (2 errors) ====
+ console.log("Hello, error!") -
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
+
+!!! error TS1109: Expression expected.
\ No newline at end of file
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-passthru.js b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-passthru.js
new file mode 100644
index 0000000000000..9650d50b1bb7c
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-filter-passthru.js
@@ -0,0 +1,5 @@
+//// [hello-error.ts]
+console.log("Hello, error!") -
+
+//// [hello-error.js]
+console.log("Hello, error!") - ;
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-override-all-functions.errors.txt b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-override-all-functions.errors.txt
new file mode 100644
index 0000000000000..63a20e9cb70e1
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-override-all-functions.errors.txt
@@ -0,0 +1,20 @@
+message program-diagnostics-replaced: Program diagnostics replaced!
+message semantic-diagnostics-replaced: Semantic diagnostics replaced!
+message syntactic-diagnostics-replaced: Syntactic diagnostics replaced!
+
+
+!!! message program-diagnostics-replaced: Program diagnostics replaced!
+!!! message semantic-diagnostics-replaced: Semantic diagnostics replaced!
+!!! message syntactic-diagnostics-replaced: Syntactic diagnostics replaced!
+==== /atotc.ts (0 errors) ====
+ It was the best of times, it was the worst of times,
+ it was the age of wisdom, it was the age of foolishness,
+ it was the epoch of belief, it was the epoch of incredulity,
+ it was the season of Light, it was the season of Darkness,
+ it was the spring of hope, it was the winter of despair,
+ we had everything before us, we had nothing before us,
+ we were all going direct to Heaven, we were all going direct
+ the other way--in short, the period was so far like the present
+ period, that some of its noisiest authorities insisted on its
+ being received, for good or for evil, in the superlative degree
+ of comparison only.
\ No newline at end of file
diff --git a/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-override-all-functions.js b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-override-all-functions.js
new file mode 100644
index 0000000000000..0a4b5fe31af18
--- /dev/null
+++ b/tests/baselines/reference/LanguageServiceHost/languageServiceExtensions/can-override-all-functions.js
@@ -0,0 +1,117 @@
+//// [atotc.ts]
+It was the best of times, it was the worst of times,
+it was the age of wisdom, it was the age of foolishness,
+it was the epoch of belief, it was the epoch of incredulity,
+it was the season of Light, it was the season of Darkness,
+it was the spring of hope, it was the winter of despair,
+we had everything before us, we had nothing before us,
+we were all going direct to Heaven, we were all going direct
+the other way--in short, the period was so far like the present
+period, that some of its noisiest authorities insisted on its
+being received, for good or for evil, in the superlative degree
+of comparison only.
+
+//// [atotc.js]
+It;
+was;
+the;
+best;
+of;
+times, it;
+was;
+the;
+worst;
+of;
+times,
+ it;
+was;
+the;
+age;
+of;
+wisdom, it;
+was;
+the;
+age;
+of;
+foolishness,
+ it;
+was;
+the;
+epoch;
+of;
+belief, it;
+was;
+the;
+epoch;
+of;
+incredulity,
+ it;
+was;
+the;
+season;
+of;
+Light, it;
+was;
+the;
+season;
+of;
+Darkness,
+ it;
+was;
+the;
+spring;
+of;
+hope, it;
+was;
+the;
+winter;
+of;
+despair,
+ we;
+had;
+everything;
+before;
+us, we;
+had;
+nothing;
+before;
+us,
+ we;
+were;
+all;
+going;
+direct;
+to;
+Heaven, we;
+were;
+all;
+going;
+direct;
+the;
+other;
+way-- in short, the;
+period;
+was;
+so;
+far;
+like;
+the;
+present;
+period, that;
+some;
+of;
+its;
+noisiest;
+authorities;
+insisted;
+on;
+its;
+being;
+received, ;
+for (good; or; )
+ for (evil, in the)
+ superlative;
+degree;
+of;
+comparison;
+only.;
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..853c922fb62eb
--- /dev/null
+++ b/tests/cases/extensions/available/extension-api/index.ts
@@ -0,0 +1,17 @@
+import * as tsi from "typescript";
+
+export abstract class LanguageServiceProvider implements tsi.LanguageServiceProvider {
+ static "extension-kind": tsi.ExtensionKind.LanguageService = "language-service";
+ protected ts: typeof tsi;
+ protected args: any;
+ protected host: tsi.LanguageServiceHost;
+ protected service: tsi.LanguageService;
+ protected registry: tsi.DocumentRegistry;
+ constructor(state: {ts: typeof tsi, args: any, host: tsi.LanguageServiceHost, service: tsi.LanguageService, registry: tsi.DocumentRegistry}) {
+ this.ts = state.ts;
+ this.args = state.args;
+ this.host = state.host;
+ this.service = state.service;
+ this.registry = state.registry;
+ }
+}
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/test-language-service/index.ts b/tests/cases/extensions/available/test-language-service/index.ts
new file mode 100644
index 0000000000000..5b64badb41002
--- /dev/null
+++ b/tests/cases/extensions/available/test-language-service/index.ts
@@ -0,0 +1,16 @@
+import {LanguageServiceProvider} from "extension-api";
+
+export default class extends LanguageServiceProvider {
+ constructor(state) { super(state); }
+ getProgramDiagnosticsFilter(previous) {
+ previous.push({
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Test language service plugin loaded!",
+ category: 2,
+ code: "test-plugin-loaded",
+ });
+ return previous;
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-language-service/package.json b/tests/cases/extensions/available/test-language-service/package.json
new file mode 100644
index 0000000000000..3003fac1bf883
--- /dev/null
+++ b/tests/cases/extensions/available/test-language-service/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "test-language-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "author": ""
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-chain/index.ts b/tests/cases/extensions/available/test-service-chain/index.ts
new file mode 100644
index 0000000000000..e89e4fbfd17b6
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-chain/index.ts
@@ -0,0 +1,71 @@
+import {LanguageServiceProvider} from "extension-api";
+
+export class AddsDiagnostics extends LanguageServiceProvider {
+ constructor(state) { super(state); }
+ getProgramDiagnosticsFilter(previous) {
+ return previous.concat([{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Program diagnostics amended!",
+ category: 2,
+ code: "program-diagnostics-amended",
+ }]);
+ }
+ getSyntacticDiagnosticsFilter(fileName, previous) {
+ return previous.concat([{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Syntactic diagnostics amended!",
+ category: 2,
+ code: "syntactic-diagnostics-amended",
+ }]);
+ }
+ getSemanticDiagnosticsFilter(fileName, previous) {
+ return previous.concat([{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Semantic diagnostics amended!",
+ category: 2,
+ code: "semantic-diagnostics-amended",
+ }]);
+ }
+}
+
+// Since this is exported second, it should be second in the chain. Probably.
+// This is honestly dependent on js host key ordering
+export class MutatesAddedDiagnostics extends LanguageServiceProvider {
+ constructor(state) { super(state); }
+ getProgramDiagnosticsFilter(previous) {
+ return previous.map(prev => prev.code === "program-diagnostics-amended" ? {
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Program diagnostics mutated!",
+ category: 2,
+ code: "program-diagnostics-mutated",
+ } : prev);
+ }
+ getSyntacticDiagnosticsFilter(fileName, previous) {
+ return previous.map(prev => prev.code === "syntactic-diagnostics-amended" ? {
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Syntactic diagnostics mutated!",
+ category: 2,
+ code: "syntactic-diagnostics-mutated",
+ } : prev);
+ }
+ getSemanticDiagnosticsFilter(fileName, previous) {
+ return previous.map(prev => prev.code === "semantic-diagnostics-amended" ? {
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Semantic diagnostics mutated!",
+ category: 2,
+ code: "semantic-diagnostics-mutated",
+ } : prev);
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-chain/package.json b/tests/cases/extensions/available/test-service-chain/package.json
new file mode 100644
index 0000000000000..e52968c9a299f
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-chain/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "test-service-chain",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "author": ""
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-filters/index.ts b/tests/cases/extensions/available/test-service-filters/index.ts
new file mode 100644
index 0000000000000..43feaf6bc0983
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-filters/index.ts
@@ -0,0 +1,134 @@
+import {LanguageServiceProvider} from "extension-api";
+
+import * as ts from "typescript";
+
+export default class extends LanguageServiceProvider {
+ constructor(state) { super(state); }
+ getProgramDiagnosticsFilter(previous) {
+ return [{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Program diagnostics replaced!",
+ category: 2,
+ code: "program-diagnostics-replaced",
+ }];
+ }
+ getSyntacticDiagnosticsFilter(fileName, previous) {
+ return [{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Syntactic diagnostics replaced!",
+ category: 2,
+ code: "syntactic-diagnostics-replaced",
+ }];
+ }
+ getSemanticDiagnosticsFilter(fileName, previous) {
+ return [{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Semantic diagnostics replaced!",
+ category: 2,
+ code: "semantic-diagnostics-replaced",
+ }];
+ }
+ getEncodedSyntacticClassificationsFilter(fileName, span, previous) {
+ return {
+ spans: [span.start, span.length, this.ts.endsWith(fileName, "atotc.ts") ? this.ts.ClassificationType.text : this.ts.ClassificationType.comment],
+ endOfLineState: this.ts.EndOfLineState.None
+ };
+ }
+ getEncodedSemanticClassificationsFilter(fileName, span, previous) {
+ return {
+ spans: [span.start, span.length, this.ts.endsWith(fileName, "atotc.ts") ? this.ts.ClassificationType.moduleName : this.ts.ClassificationType.comment],
+ endOfLineState: this.ts.EndOfLineState.None
+ };
+ }
+ getCompletionsAtPositionFilter(fileName, position, previous) {
+ return {
+ isMemberCompletion: false,
+ isNewIdentifierLocation: false,
+ entries: [{name: "fakeCompletion", kind: "", kindModifiers: "", sortText: "fakeCompletion"}]
+ };
+ }
+ getCompletionEntryDetailsFilter(fileName, position, entryName, previous) {
+ return {
+ name: "fakeCompletion",
+ kind: position.toString(),
+ kindModifiers: entryName,
+ displayParts: [],
+ documentation: [],
+ };
+ }
+ getQuickInfoAtPositionFilter(fileName, position, previous) {
+ return {};
+ }
+ getNameOrDottedNameSpanFilter(fileName, startPos, endPos, previous) {
+ return {};
+ }
+ getBreakpointStatementAtPositionFilter(fileName, position, previous) {
+ return {};
+ }
+ getSignatureHelpItemsFilter(fileName, position, previous) {
+ return {
+ items: [],
+ applicableSpan: undefined,
+ selectedItemIndex: undefined,
+ argumentIndex: 0,
+ argumentCount: 0,
+ };
+ }
+ getRenameInfoFilter(fileName, position, previous) {
+ return {};
+ }
+ findRenameLocationsFilter(fileName, position, findInStrings, findInComments, previous) {
+ return {};
+ }
+ getDefinitionAtPositionFilter(fileName, position, previous) {
+ return {};
+ }
+ getTypeDefinitionAtPositionFilter(fileName, position, previous) {
+ return {};
+ }
+ getReferencesAtPositionFilter(fileName, position, previous) {
+ return {};
+ }
+ findReferencesFilter(fileName, position, previous) {
+ return {};
+ }
+ getDocumentHighlightsFilter(fileName, position, filesToSearch, previous) {
+ return {};
+ }
+ getNavigateToItemsFilter(searchValue, maxResultCount, previous) {
+ return {};
+ }
+ getNavigationBarItemsFilter(fileName, previous) {
+ return {};
+ }
+ getOutliningSpansFilter(fileName, previous) {
+ return {};
+ }
+ getTodoCommentsFilter(fileName, descriptors, previous) {
+ return [];
+ }
+ getBraceMatchingAtPositionFilter(fileName, position, previous) {
+ return [];
+ }
+ getIndentationAtPositionFilter(fileName, position, options, previous) {
+ return {};
+ }
+ getFormattingEditsForRangeFilter(fileName, start, end, options, previous) {
+ return {};
+ }
+ getFormattingEditsForDocumentFilter(fileName, options, previous) {
+ return {};
+ }
+ getFormattingEditsAfterKeystrokeFilter(fileName, position, key, options, previous) {
+ return {};
+ }
+ getDocCommentTemplateAtPositionFilter(fileName, position, previous) {
+ return {newText: "/********Yes.*********/", caretOffset: 9};
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-filters/package.json b/tests/cases/extensions/available/test-service-filters/package.json
new file mode 100644
index 0000000000000..4afc14481f05a
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-filters/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "test-service-filters",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "author": ""
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-overrides/index.ts b/tests/cases/extensions/available/test-service-overrides/index.ts
new file mode 100644
index 0000000000000..f206583f0a029
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-overrides/index.ts
@@ -0,0 +1,133 @@
+import {LanguageServiceProvider} from "extension-api";
+import * as ts from "typescript";
+
+export default class extends LanguageServiceProvider {
+ constructor(state) { super(state); }
+ getProgramDiagnostics() {
+ return [{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Program diagnostics replaced!",
+ category: 2,
+ code: "program-diagnostics-replaced",
+ }];
+ }
+ getSyntacticDiagnostics(fileName) {
+ return [{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Syntactic diagnostics replaced!",
+ category: 2,
+ code: "syntactic-diagnostics-replaced",
+ }];
+ }
+ getSemanticDiagnostics(fileName) {
+ return [{
+ file: undefined,
+ start: undefined,
+ length: undefined,
+ messageText: "Semantic diagnostics replaced!",
+ category: 2,
+ code: "semantic-diagnostics-replaced",
+ }];
+ }
+ getEncodedSyntacticClassifications(fileName, span) {
+ return {
+ spans: [span.start, span.length, this.ts.endsWith(fileName, "atotc.ts") ? this.ts.ClassificationType.text : this.ts.ClassificationType.comment],
+ endOfLineState: this.ts.EndOfLineState.None
+ };
+ }
+ getEncodedSemanticClassifications(fileName, span) {
+ return {
+ spans: [span.start, span.length, this.ts.endsWith(fileName, "atotc.ts") ? this.ts.ClassificationType.moduleName : this.ts.ClassificationType.comment],
+ endOfLineState: this.ts.EndOfLineState.None
+ };
+ }
+ getCompletionsAtPosition(fileName, position) {
+ return {
+ isMemberCompletion: false,
+ isNewIdentifierLocation: false,
+ entries: [{name: "fakeCompletion", kind: "", kindModifiers: "", sortText: "fakeCompletion"}]
+ };
+ }
+ getCompletionEntryDetails(fileName, position, entryName) {
+ return {
+ name: "fakeCompletion",
+ kind: position.toString(),
+ kindModifiers: entryName,
+ displayParts: [],
+ documentation: [],
+ };
+ }
+ getQuickInfoAtPosition(fileName, position) {
+ return {};
+ }
+ getNameOrDottedNameSpan(fileName, startPos, endPos) {
+ return {};
+ }
+ getBreakpointStatementAtPosition(fileName, position) {
+ return {};
+ }
+ getSignatureHelpItems(fileName, position) {
+ return {
+ items: [],
+ applicableSpan: undefined,
+ selectedItemIndex: undefined,
+ argumentIndex: 0,
+ argumentCount: 0,
+ };
+ }
+ getRenameInfo(fileName, position) {
+ return {};
+ }
+ findRenameLocations(fileName, position, findInStrings, findInComments) {
+ return {};
+ }
+ getDefinitionAtPosition(fileName, position) {
+ return {};
+ }
+ getTypeDefinitionAtPosition(fileName, position) {
+ return {};
+ }
+ getReferencesAtPosition(fileName, position) {
+ return {};
+ }
+ findReferences(fileName, position) {
+ return {};
+ }
+ getDocumentHighlights(fileName, position, filesToSearch) {
+ return {};
+ }
+ getNavigateToItems(searchValue, maxResultCount) {
+ return {};
+ }
+ getNavigationBarItems(fileName) {
+ return {};
+ }
+ getOutliningSpans(fileName) {
+ return {};
+ }
+ getTodoComments(fileName, descriptors) {
+ return [];
+ }
+ getBraceMatchingAtPosition(fileName, position) {
+ return [];
+ }
+ getIndentationAtPosition(fileName, position, options) {
+ return {};
+ }
+ getFormattingEditsForRange(fileName, start, end, options) {
+ return {};
+ }
+ getFormattingEditsForDocument(fileName, options) {
+ return {};
+ }
+ getFormattingEditsAfterKeystroke(fileName, position, key, options) {
+ return {};
+ }
+ getDocCommentTemplateAtPosition(fileName, position) {
+ return {newText: "/********Yes.*********/", caretOffset: 9};
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-overrides/package.json b/tests/cases/extensions/available/test-service-overrides/package.json
new file mode 100644
index 0000000000000..92436b41974f2
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-overrides/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "test-service-overrides",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "author": ""
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-passthru/index.ts b/tests/cases/extensions/available/test-service-passthru/index.ts
new file mode 100644
index 0000000000000..074a90623815d
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-passthru/index.ts
@@ -0,0 +1,14 @@
+import {LanguageServiceProvider} from "extension-api";
+
+export default class extends LanguageServiceProvider {
+ constructor(state) { super(state); }
+ getProgramDiagnosticsFilter(previous) {
+ return previous;
+ }
+ getSyntacticDiagnosticsFilter(fileName, previous) {
+ return previous;
+ }
+ getSemanticDiagnosticsFilter(fileName, previous) {
+ return previous;
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/extensions/available/test-service-passthru/package.json b/tests/cases/extensions/available/test-service-passthru/package.json
new file mode 100644
index 0000000000000..4315a97feec91
--- /dev/null
+++ b/tests/cases/extensions/available/test-service-passthru/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "test-service-passthru",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "author": ""
+}
\ 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/fourslash/test.ts b/tests/cases/extensions/fourslash/test.ts
new file mode 100644
index 0000000000000..ee1f6094e6e4e
--- /dev/null
+++ b/tests/cases/extensions/fourslash/test.ts
@@ -0,0 +1,5 @@
+///
+
+goTo.file("hello.ts");
+goTo.marker("EOL");
+verify.caretAtMarker("EOL");
\ No newline at end of file
diff --git a/tests/cases/extensions/fourslash/verify-overridden-diagnostics.ts b/tests/cases/extensions/fourslash/verify-overridden-diagnostics.ts
new file mode 100644
index 0000000000000..5d3ca5220fd06
--- /dev/null
+++ b/tests/cases/extensions/fourslash/verify-overridden-diagnostics.ts
@@ -0,0 +1,15 @@
+///
+
+goTo.file("atotc.ts");
+verify.getSemanticDiagnostics(JSON.stringify([{message: "Semantic diagnostics replaced!", category: "message", code: "semantic-diagnostics-replaced"}], undefined, 2));
+verify.getSyntacticDiagnostics(JSON.stringify([{message: "Syntactic diagnostics replaced!", category: "message", code: "syntactic-diagnostics-replaced"}], undefined, 2));
+verify.completionListContains("fakeCompletion", "", undefined, "");
+verify.completionEntryDetailIs("fakeCompletion", "", undefined, "0");
+verify.quickInfoIs(undefined, undefined);
+verify.nameOrDottedNameSpanTextIs("");
+verify.signatureHelpArgumentCountIs(0);
+verify.definitionCountIs(0);
+verify.referencesAre([]);
+verify.todoCommentsInCurrentFile([]);
+verify.noMatchingBracePositionInCurrentFile(0);
+verify.DocCommentTemplate("/********Yes.*********/", 9);
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/atotc.ts b/tests/cases/extensions/source/atotc.ts
new file mode 100644
index 0000000000000..9818c6d2f434c
--- /dev/null
+++ b/tests/cases/extensions/source/atotc.ts
@@ -0,0 +1,11 @@
+It was the best of times, it was the worst of times,
+it was the age of wisdom, it was the age of foolishness,
+it was the epoch of belief, it was the epoch of incredulity,
+it was the season of Light, it was the season of Darkness,
+it was the spring of hope, it was the winter of despair,
+we had everything before us, we had nothing before us,
+we were all going direct to Heaven, we were all going direct
+the other way--in short, the period was so far like the present
+period, that some of its noisiest authorities insisted on its
+being received, for good or for evil, in the superlative degree
+of comparison only.
\ No newline at end of file
diff --git a/tests/cases/extensions/source/hello-error.ts b/tests/cases/extensions/source/hello-error.ts
new file mode 100644
index 0000000000000..251cc73498d66
--- /dev/null
+++ b/tests/cases/extensions/source/hello-error.ts
@@ -0,0 +1 @@
+console.log("Hello, error!") -
\ 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