mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into object-spread
This commit is contained in:
+42
-18
@@ -17,7 +17,6 @@ declare module "gulp-typescript" {
|
||||
stripInternal?: boolean;
|
||||
types?: string[];
|
||||
}
|
||||
interface CompileStream extends NodeJS.ReadWriteStream { } // Either gulp or gulp-typescript has some odd typings which don't reflect reality, making this required
|
||||
}
|
||||
import * as insert from "gulp-insert";
|
||||
import * as sourcemaps from "gulp-sourcemaps";
|
||||
@@ -169,7 +168,7 @@ for (const i in libraryTargets) {
|
||||
gulp.task(target, false, [], function() {
|
||||
return gulp.src(sources)
|
||||
.pipe(newer(target))
|
||||
.pipe(concat(target, { newLine: "" }))
|
||||
.pipe(concat(target, { newLine: "\n\n" }))
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
}
|
||||
@@ -380,10 +379,10 @@ gulp.task(builtLocalCompiler, false, [servicesFile], () => {
|
||||
return localCompilerProject.src()
|
||||
.pipe(newer(builtLocalCompiler))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsc(localCompilerProject))
|
||||
.pipe(localCompilerProject())
|
||||
.pipe(prependCopyright())
|
||||
.pipe(sourcemaps.write("."))
|
||||
.pipe(gulp.dest(builtLocalDirectory));
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => {
|
||||
@@ -391,7 +390,7 @@ gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => {
|
||||
const {js, dts} = servicesProject.src()
|
||||
.pipe(newer(servicesFile))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsc(servicesProject));
|
||||
.pipe(servicesProject());
|
||||
const completedJs = js.pipe(prependCopyright())
|
||||
.pipe(sourcemaps.write("."));
|
||||
const completedDts = dts.pipe(prependCopyright(/*outputCopyright*/true))
|
||||
@@ -409,26 +408,52 @@ gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => {
|
||||
file.path = nodeDefinitionsFile;
|
||||
return content + "\r\nexport = ts;";
|
||||
}))
|
||||
.pipe(gulp.dest(builtLocalDirectory)),
|
||||
.pipe(gulp.dest(".")),
|
||||
completedDts.pipe(clone())
|
||||
.pipe(insert.transform((content, file) => {
|
||||
file.path = nodeStandaloneDefinitionsFile;
|
||||
return content.replace(/declare (namespace|module) ts/g, 'declare module "typescript"');
|
||||
}))
|
||||
]).pipe(gulp.dest(builtLocalDirectory));
|
||||
]).pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
// cancellationToken.js
|
||||
const cancellationTokenJs = path.join(builtLocalDirectory, "cancellationToken.js");
|
||||
gulp.task(cancellationTokenJs, false, [servicesFile], () => {
|
||||
const cancellationTokenProject = tsc.createProject("src/server/cancellationToken/tsconfig.json", getCompilerSettings({}, /*useBuiltCompiler*/true));
|
||||
return cancellationTokenProject.src()
|
||||
.pipe(newer(cancellationTokenJs))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(cancellationTokenProject())
|
||||
.pipe(prependCopyright())
|
||||
.pipe(sourcemaps.write("."))
|
||||
.pipe(gulp.dest(builtLocalDirectory));
|
||||
});
|
||||
|
||||
// typingsInstallerFile.js
|
||||
const typingsInstallerJs = path.join(builtLocalDirectory, "typingsInstaller.js");
|
||||
gulp.task(typingsInstallerJs, false, [servicesFile], () => {
|
||||
const cancellationTokenProject = tsc.createProject("src/server/typingsInstaller/tsconfig.json", getCompilerSettings({}, /*useBuiltCompiler*/true));
|
||||
return cancellationTokenProject.src()
|
||||
.pipe(newer(typingsInstallerJs))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(cancellationTokenProject())
|
||||
.pipe(prependCopyright())
|
||||
.pipe(sourcemaps.write("."))
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
const serverFile = path.join(builtLocalDirectory, "tsserver.js");
|
||||
|
||||
gulp.task(serverFile, false, [servicesFile], () => {
|
||||
gulp.task(serverFile, false, [servicesFile, typingsInstallerJs, cancellationTokenJs], () => {
|
||||
const serverProject = tsc.createProject("src/server/tsconfig.json", getCompilerSettings({}, /*useBuiltCompiler*/true));
|
||||
return serverProject.src()
|
||||
.pipe(newer(serverFile))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsc(serverProject))
|
||||
.pipe(serverProject())
|
||||
.pipe(prependCopyright())
|
||||
.pipe(sourcemaps.write("."))
|
||||
.pipe(gulp.dest(builtLocalDirectory));
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
const tsserverLibraryFile = path.join(builtLocalDirectory, "tsserverlibrary.js");
|
||||
@@ -439,14 +464,14 @@ gulp.task(tsserverLibraryFile, false, [servicesFile], (done) => {
|
||||
const {js, dts}: { js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream } = serverLibraryProject.src()
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(newer(tsserverLibraryFile))
|
||||
.pipe(tsc(serverLibraryProject));
|
||||
.pipe(serverLibraryProject());
|
||||
|
||||
return merge2([
|
||||
js.pipe(prependCopyright())
|
||||
.pipe(sourcemaps.write("."))
|
||||
.pipe(gulp.dest(builtLocalDirectory)),
|
||||
.pipe(gulp.dest(".")),
|
||||
dts.pipe(prependCopyright())
|
||||
.pipe(gulp.dest(builtLocalDirectory))
|
||||
.pipe(gulp.dest("."))
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -454,7 +479,6 @@ gulp.task("lssl", "Builds language service server library", [tsserverLibraryFile
|
||||
gulp.task("local", "Builds the full compiler and services", [builtLocalCompiler, servicesFile, serverFile, builtGeneratedDiagnosticMessagesJSON, tsserverLibraryFile]);
|
||||
gulp.task("tsc", "Builds only the compiler", [builtLocalCompiler]);
|
||||
|
||||
|
||||
// Generate Markdown spec
|
||||
const word2mdJs = path.join(scriptsDirectory, "word2md.js");
|
||||
const word2mdTs = path.join(scriptsDirectory, "word2md.ts");
|
||||
@@ -493,7 +517,7 @@ gulp.task("useDebugMode", false, [], (done) => { useDebugMode = true; done(); })
|
||||
gulp.task("dontUseDebugMode", false, [], (done) => { useDebugMode = false; done(); });
|
||||
|
||||
gulp.task("VerifyLKG", false, [], () => {
|
||||
const expectedFiles = [builtLocalCompiler, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile].concat(libraryTargets);
|
||||
const expectedFiles = [builtLocalCompiler, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, typingsInstallerJs, cancellationTokenJs].concat(libraryTargets);
|
||||
const missingFiles = expectedFiles.filter(function(f) {
|
||||
return !fs.existsSync(f);
|
||||
});
|
||||
@@ -519,9 +543,9 @@ gulp.task(run, false, [servicesFile], () => {
|
||||
return testProject.src()
|
||||
.pipe(newer(run))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsc(testProject))
|
||||
.pipe(testProject())
|
||||
.pipe(sourcemaps.write(".", { includeContent: false, sourceRoot: "../../" }))
|
||||
.pipe(gulp.dest(builtLocalDirectory));
|
||||
.pipe(gulp.dest("."));
|
||||
});
|
||||
|
||||
const internalTests = "internal/";
|
||||
@@ -705,7 +729,7 @@ gulp.task("browserify", "Runs browserify on run.js to produce a file suitable fo
|
||||
return testProject.src()
|
||||
.pipe(newer("built/local/bundle.js"))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsc(testProject))
|
||||
.pipe(testProject)
|
||||
.pipe(through2.obj((file, enc, next) => {
|
||||
const originalMap = file.sourceMap;
|
||||
const prebundledContent = file.contents.toString();
|
||||
|
||||
+53
-6
@@ -11,6 +11,8 @@ var runTestsInParallel = require("./scripts/mocha-parallel").runTestsInParallel;
|
||||
var compilerDirectory = "src/compiler/";
|
||||
var servicesDirectory = "src/services/";
|
||||
var serverDirectory = "src/server/";
|
||||
var typingsInstallerDirectory = "src/server/typingsInstaller";
|
||||
var cancellationTokenDirectory = "src/server/cancellationToken";
|
||||
var harnessDirectory = "src/harness/";
|
||||
var libraryDirectory = "src/lib/";
|
||||
var scriptsDirectory = "scripts/";
|
||||
@@ -166,6 +168,13 @@ var servicesSources = [
|
||||
}));
|
||||
|
||||
var serverCoreSources = [
|
||||
"types.d.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"typingsCache.ts",
|
||||
"scriptInfo.ts",
|
||||
"lsHost.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"session.ts",
|
||||
@@ -174,12 +183,33 @@ var serverCoreSources = [
|
||||
return path.join(serverDirectory, f);
|
||||
});
|
||||
|
||||
var cancellationTokenSources = [
|
||||
"cancellationToken.ts"
|
||||
].map(function (f) {
|
||||
return path.join(cancellationTokenDirectory, f);
|
||||
});
|
||||
|
||||
var typingsInstallerSources = [
|
||||
"../types.d.ts",
|
||||
"typingsInstaller.ts",
|
||||
"nodeTypingsInstaller.ts"
|
||||
].map(function (f) {
|
||||
return path.join(typingsInstallerDirectory, f);
|
||||
});
|
||||
|
||||
var serverSources = serverCoreSources.concat(servicesSources);
|
||||
|
||||
var languageServiceLibrarySources = [
|
||||
"protocol.d.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
"lsHost.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"session.ts"
|
||||
"session.ts",
|
||||
|
||||
].map(function (f) {
|
||||
return path.join(serverDirectory, f);
|
||||
}).concat(servicesSources);
|
||||
@@ -223,15 +253,24 @@ var harnessSources = harnessCoreSources.concat([
|
||||
"convertCompilerOptionsFromJson.ts",
|
||||
"convertTypingOptionsFromJson.ts",
|
||||
"tsserverProjectSystem.ts",
|
||||
"compileOnSave.ts",
|
||||
"typingsInstaller.ts",
|
||||
"projectErrors.ts",
|
||||
"matchFiles.ts",
|
||||
"initializeTSConfig.ts",
|
||||
].map(function (f) {
|
||||
return path.join(unittestsDirectory, f);
|
||||
})).concat([
|
||||
"protocol.d.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
"lsHost.ts",
|
||||
"project.ts",
|
||||
"typingsCache.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"session.ts",
|
||||
"client.ts",
|
||||
"editorServices.ts"
|
||||
].map(function (f) {
|
||||
return path.join(serverDirectory, f);
|
||||
}));
|
||||
@@ -618,8 +657,14 @@ compileFile(
|
||||
inlineSourceMap: true
|
||||
});
|
||||
|
||||
var cancellationTokenFile = path.join(builtLocalDirectory, "cancellationToken.js");
|
||||
compileFile(cancellationTokenFile, cancellationTokenSources, [builtLocalDirectory].concat(cancellationTokenSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { outDir: builtLocalDirectory, noOutFile: true });
|
||||
|
||||
var typingsInstallerFile = path.join(builtLocalDirectory, "typingsInstaller.js");
|
||||
compileFile(typingsInstallerFile, typingsInstallerSources, [builtLocalDirectory].concat(typingsInstallerSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { outDir: builtLocalDirectory, noOutFile: false });
|
||||
|
||||
var serverFile = path.join(builtLocalDirectory, "tsserver.js");
|
||||
compileFile(serverFile, serverSources, [builtLocalDirectory, copyright].concat(serverSources), /*prefixes*/[copyright], /*useBuiltCompiler*/ true, { types: ["node"] });
|
||||
compileFile(serverFile, serverSources, [builtLocalDirectory, copyright, cancellationTokenFile, typingsInstallerFile].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { types: ["node"] });
|
||||
var tsserverLibraryFile = path.join(builtLocalDirectory, "tsserverlibrary.js");
|
||||
var tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverlibrary.d.ts");
|
||||
compileFile(
|
||||
@@ -702,7 +747,7 @@ task("generate-spec", [specMd]);
|
||||
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
|
||||
desc("Makes a new LKG out of the built js files");
|
||||
task("LKG", ["clean", "release", "local"].concat(libraryTargets), function () {
|
||||
var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile].concat(libraryTargets);
|
||||
var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile].concat(libraryTargets);
|
||||
var missingFiles = expectedFiles.filter(function (f) {
|
||||
return !fs.existsSync(f);
|
||||
});
|
||||
@@ -950,7 +995,7 @@ desc("Runs the tests using the built run.js file like 'jake runtests'. Syntax is
|
||||
task("runtests-browser", ["tests", "browserify", builtLocalDirectory, servicesFileInBrowserTest], function () {
|
||||
cleanTestDirs();
|
||||
host = "node";
|
||||
browser = process.env.browser || process.env.b || "IE";
|
||||
browser = process.env.browser || process.env.b || (os.platform() === "linux" ? "chrome" : "IE");
|
||||
tests = process.env.test || process.env.tests || process.env.t;
|
||||
var light = process.env.light || false;
|
||||
var testConfigFile = 'test.config';
|
||||
@@ -1132,6 +1177,8 @@ var lintTargets = compilerSources
|
||||
.concat(serverCoreSources)
|
||||
.concat(tslintRulesFiles)
|
||||
.concat(servicesSources)
|
||||
.concat(typingsInstallerSources)
|
||||
.concat(cancellationTokenSources)
|
||||
.concat(["Gulpfile.ts"])
|
||||
.concat([nodeServerInFile, perftscPath, "tests/perfsys.ts", webhostPath]);
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
"use strict";
|
||||
var fs = require("fs");
|
||||
function createCancellationToken(args) {
|
||||
var cancellationPipeName;
|
||||
for (var i = 0; i < args.length - 1; i++) {
|
||||
if (args[i] === "--cancellationPipeName") {
|
||||
cancellationPipeName = args[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cancellationPipeName) {
|
||||
return { isCancellationRequested: function () { return false; } };
|
||||
}
|
||||
return {
|
||||
isCancellationRequested: function () {
|
||||
try {
|
||||
fs.statSync(cancellationPipeName);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
module.exports = createCancellationToken;
|
||||
File diff suppressed because one or more lines are too long
@@ -964,7 +964,11 @@ namespace ts {
|
||||
currentFlow = preTryFlow;
|
||||
bind(node.finallyBlock);
|
||||
}
|
||||
currentFlow = finishFlowLabel(postFinallyLabel);
|
||||
// if try statement has finally block and flow after finally block is unreachable - keep it
|
||||
// otherwise use whatever flow was accumulated at postFinallyLabel
|
||||
if (!node.finallyBlock || !(currentFlow.flags & FlowFlags.Unreachable)) {
|
||||
currentFlow = finishFlowLabel(postFinallyLabel);
|
||||
}
|
||||
}
|
||||
|
||||
function bindSwitchStatement(node: SwitchStatement): void {
|
||||
@@ -2596,7 +2600,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Currently, we only support generators that were originally async function bodies.
|
||||
if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) {
|
||||
if (asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) {
|
||||
transformFlags |= TransformFlags.AssertGenerator;
|
||||
}
|
||||
|
||||
@@ -2671,7 +2675,7 @@ namespace ts {
|
||||
// down-level generator.
|
||||
// Currently we do not support transforming any other generator fucntions
|
||||
// down level.
|
||||
if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) {
|
||||
if (asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) {
|
||||
transformFlags |= TransformFlags.AssertGenerator;
|
||||
}
|
||||
}
|
||||
@@ -2702,7 +2706,7 @@ namespace ts {
|
||||
// down-level generator.
|
||||
// Currently we do not support transforming any other generator fucntions
|
||||
// down level.
|
||||
if (asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) {
|
||||
if (asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) {
|
||||
transformFlags |= TransformFlags.AssertGenerator;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/// <reference path="moduleNameResolver.ts"/>
|
||||
/// <reference path="binder.ts"/>
|
||||
|
||||
/* @internal */
|
||||
@@ -9654,8 +9655,8 @@ namespace ts {
|
||||
let container = getSuperContainer(node, /*stopOnFunctions*/ true);
|
||||
let needToCaptureLexicalThis = false;
|
||||
|
||||
// adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting
|
||||
if (!isCallExpression) {
|
||||
// adjust the container reference in case if super is used inside arrow functions with arbitrary deep nesting
|
||||
while (container && container.kind === SyntaxKind.ArrowFunction) {
|
||||
container = getSuperContainer(container, /*stopOnFunctions*/ true);
|
||||
needToCaptureLexicalThis = languageVersion < ScriptTarget.ES6;
|
||||
@@ -14710,6 +14711,7 @@ namespace ts {
|
||||
// constructors of derived classes must contain at least one super call somewhere in their function body.
|
||||
const containingClassDecl = <ClassDeclaration>node.parent;
|
||||
if (getClassExtendsHeritageClauseElement(containingClassDecl)) {
|
||||
captureLexicalThis(node.parent, containingClassDecl);
|
||||
const classExtendsNull = classDeclarationExtendsNull(containingClassDecl);
|
||||
const superCall = getSuperCallInConstructor(node);
|
||||
if (superCall) {
|
||||
@@ -17986,9 +17988,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean {
|
||||
if (node.parent.kind !== SyntaxKind.SourceFile && node.parent.kind !== SyntaxKind.ModuleBlock && node.parent.kind !== SyntaxKind.ModuleDeclaration) {
|
||||
return grammarErrorOnFirstToken(node, errorMessage);
|
||||
const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration;
|
||||
if (!isInAppropriateContext) {
|
||||
grammarErrorOnFirstToken(node, errorMessage);
|
||||
}
|
||||
return !isInAppropriateContext;
|
||||
}
|
||||
|
||||
function checkExportSpecifier(node: ExportSpecifier) {
|
||||
@@ -18029,7 +18033,7 @@ namespace ts {
|
||||
checkExpressionCached(node.expression);
|
||||
}
|
||||
|
||||
checkExternalModuleExports(<SourceFile | ModuleDeclaration>container);
|
||||
checkExternalModuleExports(container);
|
||||
|
||||
if (node.isExportEquals && !isInAmbientContext(node)) {
|
||||
if (modulekind === ModuleKind.ES6) {
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
/// <reference path="scanner.ts"/>
|
||||
|
||||
namespace ts {
|
||||
/* @internal */
|
||||
export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" };
|
||||
/* @internal */
|
||||
export const optionDeclarations: CommandLineOption[] = [
|
||||
{
|
||||
name: "charset",
|
||||
type: "string",
|
||||
},
|
||||
compileOnSaveCommandLineOption,
|
||||
{
|
||||
name: "declaration",
|
||||
shortName: "d",
|
||||
@@ -677,10 +680,10 @@ namespace ts {
|
||||
* @param fileName The path to the config file
|
||||
* @param jsonText The text of the config file
|
||||
*/
|
||||
export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } {
|
||||
export function parseConfigFileTextToJson(fileName: string, jsonText: string, stripComments = true): { config?: any; error?: Diagnostic } {
|
||||
try {
|
||||
const jsonTextWithoutComments = removeComments(jsonText);
|
||||
return { config: /\S/.test(jsonTextWithoutComments) ? JSON.parse(jsonTextWithoutComments) : {} };
|
||||
const jsonTextToParse = stripComments ? removeComments(jsonText) : jsonText;
|
||||
return { config: /\S/.test(jsonTextToParse) ? JSON.parse(jsonTextToParse) : {} };
|
||||
}
|
||||
catch (e) {
|
||||
return { error: createCompilerDiagnostic(Diagnostics.Failed_to_parse_file_0_Colon_1, fileName, e.message) };
|
||||
@@ -849,6 +852,7 @@ namespace ts {
|
||||
options.configFilePath = configFileName;
|
||||
|
||||
const { fileNames, wildcardDirectories } = getFileNames(errors);
|
||||
const compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
|
||||
|
||||
return {
|
||||
options,
|
||||
@@ -856,7 +860,8 @@ namespace ts {
|
||||
typingOptions,
|
||||
raw: json,
|
||||
errors,
|
||||
wildcardDirectories
|
||||
wildcardDirectories,
|
||||
compileOnSave
|
||||
};
|
||||
|
||||
function tryExtendsName(extendedConfig: string): [string[], string[], string[], CompilerOptions] {
|
||||
@@ -943,13 +948,24 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean {
|
||||
if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) {
|
||||
return false;
|
||||
}
|
||||
const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption["compileOnSave"], basePath, errors);
|
||||
if (typeof result === "boolean" && result) {
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } {
|
||||
const errors: Diagnostic[] = [];
|
||||
const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName);
|
||||
return { options, errors };
|
||||
}
|
||||
|
||||
export function convertTypingOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } {
|
||||
export function convertTypingOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: TypingOptions, errors: Diagnostic[] } {
|
||||
const errors: Diagnostic[] = [];
|
||||
const options = convertTypingOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName);
|
||||
return { options, errors };
|
||||
@@ -958,7 +974,9 @@ namespace ts {
|
||||
function convertCompilerOptionsFromJsonWorker(jsonOptions: any,
|
||||
basePath: string, errors: Diagnostic[], configFileName?: string): CompilerOptions {
|
||||
|
||||
const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json" ? { allowJs: true, maxNodeModuleJsDepth: 2 } : {};
|
||||
const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json"
|
||||
? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true }
|
||||
: {};
|
||||
convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_compiler_option_0, errors);
|
||||
return options;
|
||||
}
|
||||
|
||||
+24
-29
@@ -5,7 +5,7 @@ namespace ts {
|
||||
export interface CommentWriter {
|
||||
reset(): void;
|
||||
setSourceFile(sourceFile: SourceFile): void;
|
||||
emitNodeWithComments(node: Node, emitCallback: (node: Node) => void): void;
|
||||
emitNodeWithComments(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void;
|
||||
emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void): void;
|
||||
emitTrailingCommentsOfPosition(pos: number): void;
|
||||
}
|
||||
@@ -34,22 +34,24 @@ namespace ts {
|
||||
emitTrailingCommentsOfPosition,
|
||||
};
|
||||
|
||||
function emitNodeWithComments(node: Node, emitCallback: (node: Node) => void) {
|
||||
function emitNodeWithComments(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) {
|
||||
if (disabled) {
|
||||
emitCallback(node);
|
||||
emitCallback(emitContext, node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node) {
|
||||
const { pos, end } = node.commentRange || node;
|
||||
const emitFlags = node.emitFlags;
|
||||
const { pos, end } = getCommentRange(node);
|
||||
const emitFlags = getEmitFlags(node);
|
||||
if ((pos < 0 && end < 0) || (pos === end)) {
|
||||
// Both pos and end are synthesized, so just emit the node without comments.
|
||||
if (emitFlags & NodeEmitFlags.NoNestedComments) {
|
||||
disableCommentsAndEmit(node, emitCallback);
|
||||
if (emitFlags & EmitFlags.NoNestedComments) {
|
||||
disabled = true;
|
||||
emitCallback(emitContext, node);
|
||||
disabled = false;
|
||||
}
|
||||
else {
|
||||
emitCallback(node);
|
||||
emitCallback(emitContext, node);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -58,8 +60,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement;
|
||||
const skipLeadingComments = pos < 0 || (emitFlags & NodeEmitFlags.NoLeadingComments) !== 0;
|
||||
const skipTrailingComments = end < 0 || (emitFlags & NodeEmitFlags.NoTrailingComments) !== 0;
|
||||
const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0;
|
||||
const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0;
|
||||
|
||||
// Emit leading comments if the position is not synthesized and the node
|
||||
// has not opted out from emitting leading comments.
|
||||
@@ -90,11 +92,13 @@ namespace ts {
|
||||
performance.measure("commentTime", "preEmitNodeWithComment");
|
||||
}
|
||||
|
||||
if (emitFlags & NodeEmitFlags.NoNestedComments) {
|
||||
disableCommentsAndEmit(node, emitCallback);
|
||||
if (emitFlags & EmitFlags.NoNestedComments) {
|
||||
disabled = true;
|
||||
emitCallback(emitContext, node);
|
||||
disabled = false;
|
||||
}
|
||||
else {
|
||||
emitCallback(node);
|
||||
emitCallback(emitContext, node);
|
||||
}
|
||||
|
||||
if (extendedDiagnostics) {
|
||||
@@ -125,9 +129,9 @@ namespace ts {
|
||||
}
|
||||
|
||||
const { pos, end } = detachedRange;
|
||||
const emitFlags = node.emitFlags;
|
||||
const skipLeadingComments = pos < 0 || (emitFlags & NodeEmitFlags.NoLeadingComments) !== 0;
|
||||
const skipTrailingComments = disabled || end < 0 || (emitFlags & NodeEmitFlags.NoTrailingComments) !== 0;
|
||||
const emitFlags = getEmitFlags(node);
|
||||
const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0;
|
||||
const skipTrailingComments = disabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0;
|
||||
|
||||
if (!skipLeadingComments) {
|
||||
emitDetachedCommentsAndUpdateCommentsInfo(detachedRange);
|
||||
@@ -137,8 +141,10 @@ namespace ts {
|
||||
performance.measure("commentTime", "preEmitBodyWithDetachedComments");
|
||||
}
|
||||
|
||||
if (emitFlags & NodeEmitFlags.NoNestedComments) {
|
||||
disableCommentsAndEmit(node, emitCallback);
|
||||
if (emitFlags & EmitFlags.NoNestedComments && !disabled) {
|
||||
disabled = true;
|
||||
emitCallback(node);
|
||||
disabled = false;
|
||||
}
|
||||
else {
|
||||
emitCallback(node);
|
||||
@@ -284,17 +290,6 @@ namespace ts {
|
||||
detachedCommentsInfo = undefined;
|
||||
}
|
||||
|
||||
function disableCommentsAndEmit(node: Node, emitCallback: (node: Node) => void): void {
|
||||
if (disabled) {
|
||||
emitCallback(node);
|
||||
}
|
||||
else {
|
||||
disabled = true;
|
||||
emitCallback(node);
|
||||
disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasDetachedComments(pos: number) {
|
||||
return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos;
|
||||
}
|
||||
|
||||
+212
-6
@@ -1,7 +1,6 @@
|
||||
/// <reference path="types.ts"/>
|
||||
/// <reference path="types.ts"/>
|
||||
/// <reference path="performance.ts" />
|
||||
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
/**
|
||||
@@ -47,6 +46,7 @@ namespace ts {
|
||||
contains,
|
||||
remove,
|
||||
forEachValue: forEachValueInMap,
|
||||
getKeys,
|
||||
clear,
|
||||
};
|
||||
|
||||
@@ -56,6 +56,14 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getKeys() {
|
||||
const keys: Path[] = [];
|
||||
for (const key in files) {
|
||||
keys.push(<Path>key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// path should already be well-formed so it does not need to be normalized
|
||||
function get(path: Path): T {
|
||||
return files[toKey(path)];
|
||||
@@ -397,6 +405,7 @@ namespace ts {
|
||||
return [...array1, ...array2];
|
||||
}
|
||||
|
||||
// TODO: fixme (N^2) - add optional comparer so collection can be sorted before deduplication.
|
||||
export function deduplicate<T>(array: T[], areEqual?: (a: T, b: T) => boolean): T[] {
|
||||
let result: T[];
|
||||
if (array) {
|
||||
@@ -496,18 +505,25 @@ namespace ts {
|
||||
* @param array A sorted array whose first element must be no larger than number
|
||||
* @param number The value to be searched for in the array.
|
||||
*/
|
||||
export function binarySearch(array: number[], value: number): number {
|
||||
export function binarySearch<T>(array: T[], value: T, comparer?: (v1: T, v2: T) => number): number {
|
||||
if (!array || array.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let low = 0;
|
||||
let high = array.length - 1;
|
||||
comparer = comparer !== undefined
|
||||
? comparer
|
||||
: (v1, v2) => (v1 < v2 ? -1 : (v1 > v2 ? 1 : 0));
|
||||
|
||||
while (low <= high) {
|
||||
const middle = low + ((high - low) >> 1);
|
||||
const midValue = array[middle];
|
||||
|
||||
if (midValue === value) {
|
||||
if (comparer(midValue, value) === 0) {
|
||||
return middle;
|
||||
}
|
||||
else if (midValue > value) {
|
||||
else if (comparer(midValue, value) > 0) {
|
||||
high = middle - 1;
|
||||
}
|
||||
else {
|
||||
@@ -824,6 +840,72 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* High-order function, creates a function that executes a function composition.
|
||||
* For example, `chain(a, b)` is the equivalent of `x => ((a', b') => y => b'(a'(y)))(a(x), b(x))`
|
||||
*
|
||||
* @param args The functions to chain.
|
||||
*/
|
||||
export function chain<T, U>(...args: ((t: T) => (u: U) => U)[]): (t: T) => (u: U) => U;
|
||||
export function chain<T, U>(a: (t: T) => (u: U) => U, b: (t: T) => (u: U) => U, c: (t: T) => (u: U) => U, d: (t: T) => (u: U) => U, e: (t: T) => (u: U) => U): (t: T) => (u: U) => U {
|
||||
if (e) {
|
||||
const args: ((t: T) => (u: U) => U)[] = [];
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
}
|
||||
|
||||
return t => compose(...map(args, f => f(t)));
|
||||
}
|
||||
else if (d) {
|
||||
return t => compose(a(t), b(t), c(t), d(t));
|
||||
}
|
||||
else if (c) {
|
||||
return t => compose(a(t), b(t), c(t));
|
||||
}
|
||||
else if (b) {
|
||||
return t => compose(a(t), b(t));
|
||||
}
|
||||
else if (a) {
|
||||
return t => compose(a(t));
|
||||
}
|
||||
else {
|
||||
return t => u => u;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High-order function, composes functions. Note that functions are composed inside-out;
|
||||
* for example, `compose(a, b)` is the equivalent of `x => b(a(x))`.
|
||||
*
|
||||
* @param args The functions to compose.
|
||||
*/
|
||||
export function compose<T>(...args: ((t: T) => T)[]): (t: T) => T;
|
||||
export function compose<T>(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T {
|
||||
if (e) {
|
||||
const args: ((t: T) => T)[] = [];
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
}
|
||||
|
||||
return t => reduceLeft<(t: T) => T, T>(args, (u, f) => f(u), t);
|
||||
}
|
||||
else if (d) {
|
||||
return t => d(c(b(a(t))));
|
||||
}
|
||||
else if (c) {
|
||||
return t => c(b(a(t)));
|
||||
}
|
||||
else if (b) {
|
||||
return t => b(a(t));
|
||||
}
|
||||
else if (a) {
|
||||
return t => a(t);
|
||||
}
|
||||
else {
|
||||
return t => t;
|
||||
}
|
||||
}
|
||||
|
||||
function formatStringFromArgs(text: string, args: { [index: number]: any; }, baseIndex?: number): string {
|
||||
baseIndex = baseIndex || 0;
|
||||
|
||||
@@ -1096,10 +1178,49 @@ namespace ts {
|
||||
return path && !isRootedDiskPath(path) && path.indexOf("://") !== -1;
|
||||
}
|
||||
|
||||
export function isExternalModuleNameRelative(moduleName: string): boolean {
|
||||
// TypeScript 1.0 spec (April 2014): 11.2.1
|
||||
// An external module name is "relative" if the first term is "." or "..".
|
||||
return /^\.\.?($|[\\/])/.test(moduleName);
|
||||
}
|
||||
|
||||
export function getEmitScriptTarget(compilerOptions: CompilerOptions) {
|
||||
return compilerOptions.target || ScriptTarget.ES3;
|
||||
}
|
||||
|
||||
export function getEmitModuleKind(compilerOptions: CompilerOptions) {
|
||||
return typeof compilerOptions.module === "number" ?
|
||||
compilerOptions.module :
|
||||
getEmitScriptTarget(compilerOptions) === ScriptTarget.ES6 ? ModuleKind.ES6 : ModuleKind.CommonJS;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
let seenAsterisk = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) === CharacterCodes.asterisk) {
|
||||
if (!seenAsterisk) {
|
||||
seenAsterisk = true;
|
||||
}
|
||||
else {
|
||||
// have already seen asterisk
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isRootedDiskPath(path: string) {
|
||||
return getRootLength(path) !== 0;
|
||||
}
|
||||
|
||||
export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
|
||||
return !isRootedDiskPath(absoluteOrRelativePath)
|
||||
? absoluteOrRelativePath
|
||||
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
|
||||
}
|
||||
|
||||
function normalizedPathComponents(path: string, rootLength: number) {
|
||||
const normalizedParts = getNormalizedParts(path, rootLength);
|
||||
return [path.substr(0, rootLength)].concat(normalizedParts);
|
||||
@@ -1591,6 +1712,14 @@ namespace ts {
|
||||
return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions;
|
||||
}
|
||||
|
||||
export function hasJavaScriptFileExtension(fileName: string) {
|
||||
return forEach(supportedJavascriptExtensions, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
|
||||
export function hasTypeScriptFileExtension(fileName: string) {
|
||||
return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
|
||||
export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions) {
|
||||
if (!fileName) { return false; }
|
||||
|
||||
@@ -1715,7 +1844,6 @@ namespace ts {
|
||||
this.transformFlags = TransformFlags.None;
|
||||
this.parent = undefined;
|
||||
this.original = undefined;
|
||||
this.transformId = 0;
|
||||
}
|
||||
|
||||
export let objectAllocator: ObjectAllocator = {
|
||||
@@ -1825,4 +1953,82 @@ namespace ts {
|
||||
? ((fileName) => fileName)
|
||||
: ((fileName) => fileName.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* patternStrings contains both pattern strings (containing "*") and regular strings.
|
||||
* Return an exact match if possible, or a pattern match, or undefined.
|
||||
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
|
||||
*/
|
||||
/* @internal */
|
||||
export function matchPatternOrExact(patternStrings: string[], candidate: string): string | Pattern | undefined {
|
||||
const patterns: Pattern[] = [];
|
||||
for (const patternString of patternStrings) {
|
||||
const pattern = tryParsePattern(patternString);
|
||||
if (pattern) {
|
||||
patterns.push(pattern);
|
||||
}
|
||||
else if (patternString === candidate) {
|
||||
// pattern was matched as is - no need to search further
|
||||
return patternString;
|
||||
}
|
||||
}
|
||||
|
||||
return findBestPatternMatch(patterns, _ => _, candidate);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function patternText({prefix, suffix}: Pattern): string {
|
||||
return `${prefix}*${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that candidate matches pattern, returns the text matching the '*'.
|
||||
* E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar"
|
||||
*/
|
||||
/* @internal */
|
||||
export function matchedText(pattern: Pattern, candidate: string): string {
|
||||
Debug.assert(isPatternMatch(pattern, candidate));
|
||||
return candidate.substr(pattern.prefix.length, candidate.length - pattern.suffix.length);
|
||||
}
|
||||
|
||||
/** Return the object corresponding to the best pattern to match `candidate`. */
|
||||
/* @internal */
|
||||
export function findBestPatternMatch<T>(values: T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined {
|
||||
let matchedValue: T | undefined = undefined;
|
||||
// use length of prefix as betterness criteria
|
||||
let longestMatchPrefixLength = -1;
|
||||
|
||||
for (const v of values) {
|
||||
const pattern = getPattern(v);
|
||||
if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) {
|
||||
longestMatchPrefixLength = pattern.prefix.length;
|
||||
matchedValue = v;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedValue;
|
||||
}
|
||||
|
||||
function isPatternMatch({prefix, suffix}: Pattern, candidate: string) {
|
||||
return candidate.length >= prefix.length + suffix.length &&
|
||||
startsWith(candidate, prefix) &&
|
||||
endsWith(candidate, suffix);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function tryParsePattern(pattern: string): Pattern | undefined {
|
||||
// This should be verified outside of here and a proper error thrown.
|
||||
Debug.assert(hasZeroOrOneAsteriskCharacter(pattern));
|
||||
const indexOfStar = pattern.indexOf("*");
|
||||
return indexOfStar === -1 ? undefined : {
|
||||
prefix: pattern.substr(0, indexOfStar),
|
||||
suffix: pattern.substr(indexOfStar + 1)
|
||||
};
|
||||
}
|
||||
|
||||
export function positionIsSynthesized(pos: number): boolean {
|
||||
// This is a fast way of testing the following conditions:
|
||||
// pos === undefined || pos === null || isNaN(pos) || pos < 0;
|
||||
return !(pos >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,12 @@ namespace ts {
|
||||
return declarationDiagnostics.getDiagnostics(targetSourceFile ? targetSourceFile.fileName : undefined);
|
||||
|
||||
function getDeclarationDiagnosticsFromFile({ declarationFilePath }: EmitFileNames, sources: SourceFile[], isBundledEmit: boolean) {
|
||||
emitDeclarations(host, resolver, declarationDiagnostics, declarationFilePath, sources, isBundledEmit);
|
||||
emitDeclarations(host, resolver, declarationDiagnostics, declarationFilePath, sources, isBundledEmit, /*emitOnlyDtsFiles*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
function emitDeclarations(host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, declarationFilePath: string,
|
||||
sourceFiles: SourceFile[], isBundledEmit: boolean): DeclarationEmit {
|
||||
sourceFiles: SourceFile[], isBundledEmit: boolean, emitOnlyDtsFiles: boolean): DeclarationEmit {
|
||||
const newLine = host.getNewLine();
|
||||
const compilerOptions = host.getCompilerOptions();
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace ts {
|
||||
// global file reference is added only
|
||||
// - if it is not bundled emit (because otherwise it would be self reference)
|
||||
// - and it is not already added
|
||||
if (writeReferencePath(referencedFile, !isBundledEmit && !addedGlobalFileReference)) {
|
||||
if (writeReferencePath(referencedFile, !isBundledEmit && !addedGlobalFileReference, emitOnlyDtsFiles)) {
|
||||
addedGlobalFileReference = true;
|
||||
}
|
||||
emittedReferencedFiles.push(referencedFile);
|
||||
@@ -327,7 +327,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
errorNameNode = declaration.name;
|
||||
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction, writer);
|
||||
resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
|
||||
errorNameNode = undefined;
|
||||
}
|
||||
}
|
||||
@@ -341,7 +341,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
errorNameNode = signature.name;
|
||||
resolver.writeReturnTypeOfSignatureDeclaration(signature, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction, writer);
|
||||
resolver.writeReturnTypeOfSignatureDeclaration(signature, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
|
||||
errorNameNode = undefined;
|
||||
}
|
||||
}
|
||||
@@ -563,7 +563,7 @@ namespace ts {
|
||||
write(tempVarName);
|
||||
write(": ");
|
||||
writer.getSymbolAccessibilityDiagnostic = getDefaultExportAccessibilityDiagnostic;
|
||||
resolver.writeTypeOfExpression(node.expression, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction, writer);
|
||||
resolver.writeTypeOfExpression(node.expression, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
|
||||
write(";");
|
||||
writeLine();
|
||||
write(node.isExportEquals ? "export = " : "export default ");
|
||||
@@ -1026,7 +1026,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
writer.getSymbolAccessibilityDiagnostic = getHeritageClauseVisibilityError;
|
||||
resolver.writeBaseConstructorTypeOfClass(<ClassLikeDeclaration>enclosingDeclaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction, writer);
|
||||
resolver.writeBaseConstructorTypeOfClass(<ClassLikeDeclaration>enclosingDeclaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer);
|
||||
}
|
||||
|
||||
function getHeritageClauseVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic {
|
||||
@@ -1730,7 +1730,7 @@ namespace ts {
|
||||
* @param referencedFile
|
||||
* @param addBundledFileReference Determines if global file reference corresponding to bundled file should be emitted or not
|
||||
*/
|
||||
function writeReferencePath(referencedFile: SourceFile, addBundledFileReference: boolean): boolean {
|
||||
function writeReferencePath(referencedFile: SourceFile, addBundledFileReference: boolean, emitOnlyDtsFiles: boolean): boolean {
|
||||
let declFileName: string;
|
||||
let addedBundledEmitReference = false;
|
||||
if (isDeclarationFile(referencedFile)) {
|
||||
@@ -1739,7 +1739,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// Get the declaration file path
|
||||
forEachExpectedEmitFile(host, getDeclFileName, referencedFile);
|
||||
forEachExpectedEmitFile(host, getDeclFileName, referencedFile, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
if (declFileName) {
|
||||
@@ -1768,8 +1768,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function writeDeclarationFile(declarationFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection) {
|
||||
const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFiles, isBundledEmit);
|
||||
export function writeDeclarationFile(declarationFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, emitOnlyDtsFiles: boolean) {
|
||||
const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFiles, isBundledEmit, emitOnlyDtsFiles);
|
||||
const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath) || host.getCompilerOptions().noEmit;
|
||||
if (!emitSkipped) {
|
||||
const declarationOutput = emitDeclarationResult.referencesOutput
|
||||
|
||||
@@ -2869,6 +2869,10 @@
|
||||
"category": "Message",
|
||||
"code": 6139
|
||||
},
|
||||
"Auto discovery for typings is enabled in project '{0}'. Running extra resolution pass for module '{1}' using cache location '{2}'.": {
|
||||
"category": "Error",
|
||||
"code": 6140
|
||||
},
|
||||
"Variable '{0}' implicitly has an '{1}' type.": {
|
||||
"category": "Error",
|
||||
"code": 7005
|
||||
|
||||
+169
-251
@@ -13,8 +13,11 @@ namespace ts {
|
||||
_i = 0x10000000, // Use/preference flag for '_i'
|
||||
}
|
||||
|
||||
const id = (s: SourceFile) => s;
|
||||
const nullTransformers: Transformer[] = [ctx => id];
|
||||
|
||||
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
|
||||
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult {
|
||||
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean): EmitResult {
|
||||
const delimiters = createDelimiterMap();
|
||||
const brackets = createBracketsMap();
|
||||
|
||||
@@ -192,7 +195,7 @@ const _super = (function (geti, seti) {
|
||||
const emittedFilesList: string[] = compilerOptions.listEmittedFiles ? [] : undefined;
|
||||
const emitterDiagnostics = createDiagnosticCollection();
|
||||
const newLine = host.getNewLine();
|
||||
const transformers = getTransformers(compilerOptions);
|
||||
const transformers: Transformer[] = emitOnlyDtsFiles ? nullTransformers : getTransformers(compilerOptions);
|
||||
const writer = createTextWriter(newLine);
|
||||
const {
|
||||
write,
|
||||
@@ -203,10 +206,8 @@ const _super = (function (geti, seti) {
|
||||
|
||||
const sourceMap = createSourceMapWriter(host, writer);
|
||||
const {
|
||||
emitStart,
|
||||
emitEnd,
|
||||
emitTokenStart,
|
||||
emitTokenEnd
|
||||
emitNodeWithSourceMap,
|
||||
emitTokenWithSourceMap
|
||||
} = sourceMap;
|
||||
|
||||
const comments = createCommentWriter(host, writer, sourceMap);
|
||||
@@ -231,36 +232,27 @@ const _super = (function (geti, seti) {
|
||||
let isOwnFileEmit: boolean;
|
||||
let emitSkipped = false;
|
||||
|
||||
performance.mark("beforeTransform");
|
||||
const sourceFiles = getSourceFilesToEmit(host, targetSourceFile);
|
||||
|
||||
// Transform the source files
|
||||
const transformed = transformFiles(
|
||||
resolver,
|
||||
host,
|
||||
getSourceFilesToEmit(host, targetSourceFile),
|
||||
transformers);
|
||||
|
||||
performance.mark("beforeTransform");
|
||||
const {
|
||||
transformed,
|
||||
emitNodeWithSubstitution,
|
||||
emitNodeWithNotification
|
||||
} = transformFiles(resolver, host, sourceFiles, transformers);
|
||||
performance.measure("transformTime", "beforeTransform");
|
||||
|
||||
// Extract helpers from the result
|
||||
const {
|
||||
getTokenSourceMapRange,
|
||||
isSubstitutionEnabled,
|
||||
isEmitNotificationEnabled,
|
||||
onSubstituteNode,
|
||||
onEmitNode
|
||||
} = transformed;
|
||||
|
||||
performance.mark("beforePrint");
|
||||
|
||||
// Emit each output file
|
||||
forEachTransformedEmitFile(host, transformed.getSourceFiles(), emitFile);
|
||||
|
||||
// Clean up after transformation
|
||||
transformed.dispose();
|
||||
|
||||
performance.mark("beforePrint");
|
||||
forEachTransformedEmitFile(host, transformed, emitFile, emitOnlyDtsFiles);
|
||||
performance.measure("printTime", "beforePrint");
|
||||
|
||||
// Clean up emit nodes on parse tree
|
||||
for (const sourceFile of sourceFiles) {
|
||||
disposeEmitNodes(sourceFile);
|
||||
}
|
||||
|
||||
return {
|
||||
emitSkipped,
|
||||
diagnostics: emitterDiagnostics.getDiagnostics(),
|
||||
@@ -271,18 +263,22 @@ const _super = (function (geti, seti) {
|
||||
function emitFile(jsFilePath: string, sourceMapFilePath: string, declarationFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) {
|
||||
// Make sure not to write js file and source map file if any of them cannot be written
|
||||
if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) {
|
||||
printFile(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
if (!emitOnlyDtsFiles) {
|
||||
printFile(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
}
|
||||
}
|
||||
else {
|
||||
emitSkipped = true;
|
||||
}
|
||||
|
||||
if (declarationFilePath) {
|
||||
emitSkipped = writeDeclarationFile(declarationFilePath, getOriginalSourceFiles(sourceFiles), isBundledEmit, host, resolver, emitterDiagnostics) || emitSkipped;
|
||||
emitSkipped = writeDeclarationFile(declarationFilePath, getOriginalSourceFiles(sourceFiles), isBundledEmit, host, resolver, emitterDiagnostics, emitOnlyDtsFiles) || emitSkipped;
|
||||
}
|
||||
|
||||
if (!emitSkipped && emittedFilesList) {
|
||||
emittedFilesList.push(jsFilePath);
|
||||
if (!emitOnlyDtsFiles) {
|
||||
emittedFilesList.push(jsFilePath);
|
||||
}
|
||||
if (sourceMapFilePath) {
|
||||
emittedFilesList.push(sourceMapFilePath);
|
||||
}
|
||||
@@ -351,154 +347,137 @@ const _super = (function (geti, seti) {
|
||||
currentFileIdentifiers = node.identifiers;
|
||||
sourceMap.setSourceFile(node);
|
||||
comments.setSourceFile(node);
|
||||
emitNodeWithNotification(node, emitWorker);
|
||||
pipelineEmitWithNotification(EmitContext.SourceFile, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node.
|
||||
*/
|
||||
function emit(node: Node) {
|
||||
emitNodeWithNotification(node, emitWithComments);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Emits a node with comments.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from emit.
|
||||
*/
|
||||
function emitWithComments(node: Node) {
|
||||
emitNodeWithComments(node, emitWithSourceMap);
|
||||
pipelineEmitWithNotification(EmitContext.Unspecified, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node with source maps.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from emitWithComments.
|
||||
* Emits an IdentifierName.
|
||||
*/
|
||||
function emitWithSourceMap(node: Node) {
|
||||
emitNodeWithSourceMap(node, emitWorker);
|
||||
}
|
||||
|
||||
function emitIdentifierName(node: Identifier) {
|
||||
if (node) {
|
||||
emitNodeWithNotification(node, emitIdentifierNameWithComments);
|
||||
}
|
||||
}
|
||||
|
||||
function emitIdentifierNameWithComments(node: Identifier) {
|
||||
emitNodeWithComments(node, emitWorker);
|
||||
pipelineEmitWithNotification(EmitContext.IdentifierName, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an expression node.
|
||||
*/
|
||||
function emitExpression(node: Expression) {
|
||||
emitNodeWithNotification(node, emitExpressionWithComments);
|
||||
pipelineEmitWithNotification(EmitContext.Expression, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an expression with comments.
|
||||
* Emits a node with possible notification.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emitExpression pipeline
|
||||
* and should only be called indirectly from emitExpression.
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called from printSourceFile, emit, emitExpression, or
|
||||
* emitIdentifierName.
|
||||
*/
|
||||
function emitExpressionWithComments(node: Expression) {
|
||||
emitNodeWithComments(node, emitExpressionWithSourceMap);
|
||||
function pipelineEmitWithNotification(emitContext: EmitContext, node: Node) {
|
||||
emitNodeWithNotification(emitContext, node, pipelineEmitWithComments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an expression with source maps.
|
||||
* Emits a node with comments.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emitExpression pipeline
|
||||
* and should only be called indirectly from emitExpressionWithComments.
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitWithNotification.
|
||||
*/
|
||||
function emitExpressionWithSourceMap(node: Expression) {
|
||||
emitNodeWithSourceMap(node, emitExpressionWorker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node with emit notification if available.
|
||||
*/
|
||||
function emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void) {
|
||||
if (node) {
|
||||
if (isEmitNotificationEnabled(node)) {
|
||||
onEmitNode(node, emitCallback);
|
||||
}
|
||||
else {
|
||||
emitCallback(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void) {
|
||||
if (node) {
|
||||
emitStart(/*range*/ node, /*contextNode*/ node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange);
|
||||
emitCallback(node);
|
||||
emitEnd(/*range*/ node, /*contextNode*/ node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange);
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceMapRange(node: Node) {
|
||||
return node.sourceMapRange || node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to skip leading comment emit for a node.
|
||||
*
|
||||
* We do not emit comments for NotEmittedStatement nodes or any node that has
|
||||
* NodeEmitFlags.NoLeadingComments.
|
||||
*
|
||||
* @param node A Node.
|
||||
*/
|
||||
function shouldSkipLeadingCommentsForNode(node: Node) {
|
||||
return isNotEmittedStatement(node)
|
||||
|| (node.emitFlags & NodeEmitFlags.NoLeadingComments) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to skip source map emit for the start position of a node.
|
||||
*
|
||||
* We do not emit source maps for NotEmittedStatement nodes or any node that
|
||||
* has NodeEmitFlags.NoLeadingSourceMap.
|
||||
*
|
||||
* @param node A Node.
|
||||
*/
|
||||
function shouldSkipLeadingSourceMapForNode(node: Node) {
|
||||
return isNotEmittedStatement(node)
|
||||
|| (node.emitFlags & NodeEmitFlags.NoLeadingSourceMap) !== 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether to skip source map emit for the end position of a node.
|
||||
*
|
||||
* We do not emit source maps for NotEmittedStatement nodes or any node that
|
||||
* has NodeEmitFlags.NoTrailingSourceMap.
|
||||
*
|
||||
* @param node A Node.
|
||||
*/
|
||||
function shouldSkipTrailingSourceMapForNode(node: Node) {
|
||||
return isNotEmittedStatement(node)
|
||||
|| (node.emitFlags & NodeEmitFlags.NoTrailingSourceMap) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to skip source map emit for a node and its children.
|
||||
*
|
||||
* We do not emit source maps for a node that has NodeEmitFlags.NoNestedSourceMaps.
|
||||
*/
|
||||
function shouldSkipSourceMapForChildren(node: Node) {
|
||||
return (node.emitFlags & NodeEmitFlags.NoNestedSourceMaps) !== 0;
|
||||
}
|
||||
|
||||
function emitWorker(node: Node): void {
|
||||
if (tryEmitSubstitute(node, emitWorker, /*isExpression*/ false)) {
|
||||
function pipelineEmitWithComments(emitContext: EmitContext, node: Node) {
|
||||
// Do not emit comments for SourceFile
|
||||
if (emitContext === EmitContext.SourceFile) {
|
||||
pipelineEmitWithSourceMap(emitContext, node);
|
||||
return;
|
||||
}
|
||||
|
||||
emitNodeWithComments(emitContext, node, pipelineEmitWithSourceMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node with source maps.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitWithComments.
|
||||
*/
|
||||
function pipelineEmitWithSourceMap(emitContext: EmitContext, node: Node) {
|
||||
// Do not emit source mappings for SourceFile or IdentifierName
|
||||
if (emitContext === EmitContext.SourceFile
|
||||
|| emitContext === EmitContext.IdentifierName) {
|
||||
pipelineEmitWithSubstitution(emitContext, node);
|
||||
return;
|
||||
}
|
||||
|
||||
emitNodeWithSourceMap(emitContext, node, pipelineEmitWithSubstitution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node with possible substitution.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitWithSourceMap or
|
||||
* pipelineEmitInUnspecifiedContext (when picking a more specific context).
|
||||
*/
|
||||
function pipelineEmitWithSubstitution(emitContext: EmitContext, node: Node) {
|
||||
emitNodeWithSubstitution(emitContext, node, pipelineEmitForContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitWithSubstitution.
|
||||
*/
|
||||
function pipelineEmitForContext(emitContext: EmitContext, node: Node): void {
|
||||
switch (emitContext) {
|
||||
case EmitContext.SourceFile: return pipelineEmitInSourceFileContext(node);
|
||||
case EmitContext.IdentifierName: return pipelineEmitInIdentifierNameContext(node);
|
||||
case EmitContext.Unspecified: return pipelineEmitInUnspecifiedContext(node);
|
||||
case EmitContext.Expression: return pipelineEmitInExpressionContext(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node in the SourceFile EmitContext.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitForContext.
|
||||
*/
|
||||
function pipelineEmitInSourceFileContext(node: Node): void {
|
||||
const kind = node.kind;
|
||||
switch (kind) {
|
||||
// Top-level nodes
|
||||
case SyntaxKind.SourceFile:
|
||||
return emitSourceFile(<SourceFile>node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node in the IdentifierName EmitContext.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitForContext.
|
||||
*/
|
||||
function pipelineEmitInIdentifierNameContext(node: Node): void {
|
||||
const kind = node.kind;
|
||||
switch (kind) {
|
||||
// Identifiers
|
||||
case SyntaxKind.Identifier:
|
||||
return emitIdentifier(<Identifier>node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node in the Unspecified EmitContext.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitForContext.
|
||||
*/
|
||||
function pipelineEmitInUnspecifiedContext(node: Node): void {
|
||||
const kind = node.kind;
|
||||
switch (kind) {
|
||||
// Pseudo-literals
|
||||
@@ -534,7 +513,8 @@ const _super = (function (geti, seti) {
|
||||
case SyntaxKind.StringKeyword:
|
||||
case SyntaxKind.SymbolKeyword:
|
||||
case SyntaxKind.GlobalKeyword:
|
||||
return writeTokenNode(node);
|
||||
writeTokenText(kind);
|
||||
return;
|
||||
|
||||
// Parse tree nodes
|
||||
|
||||
@@ -739,25 +719,24 @@ const _super = (function (geti, seti) {
|
||||
case SyntaxKind.EnumMember:
|
||||
return emitEnumMember(<EnumMember>node);
|
||||
|
||||
// Top-level nodes
|
||||
case SyntaxKind.SourceFile:
|
||||
return emitSourceFile(<SourceFile>node);
|
||||
|
||||
// JSDoc nodes (ignored)
|
||||
|
||||
// Transformation nodes (ignored)
|
||||
}
|
||||
|
||||
// If the node is an expression, try to emit it as an expression with
|
||||
// substitution.
|
||||
if (isExpression(node)) {
|
||||
return emitExpressionWorker(node);
|
||||
return pipelineEmitWithSubstitution(EmitContext.Expression, node);
|
||||
}
|
||||
}
|
||||
|
||||
function emitExpressionWorker(node: Node) {
|
||||
if (tryEmitSubstitute(node, emitExpressionWorker, /*isExpression*/ true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a node in the Expression EmitContext.
|
||||
*
|
||||
* NOTE: Do not call this method directly. It is part of the emit pipeline
|
||||
* and should only be called indirectly from pipelineEmitForContext.
|
||||
*/
|
||||
function pipelineEmitInExpressionContext(node: Node): void {
|
||||
const kind = node.kind;
|
||||
switch (kind) {
|
||||
// Literals
|
||||
@@ -779,7 +758,8 @@ const _super = (function (geti, seti) {
|
||||
case SyntaxKind.SuperKeyword:
|
||||
case SyntaxKind.TrueKeyword:
|
||||
case SyntaxKind.ThisKeyword:
|
||||
return writeTokenNode(node);
|
||||
writeTokenText(kind);
|
||||
return;
|
||||
|
||||
// Expressions
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
@@ -881,7 +861,7 @@ const _super = (function (geti, seti) {
|
||||
//
|
||||
|
||||
function emitIdentifier(node: Identifier) {
|
||||
if (node.emitFlags & NodeEmitFlags.UMDDefine) {
|
||||
if (getEmitFlags(node) & EmitFlags.UMDDefine) {
|
||||
writeLines(umdHelper);
|
||||
}
|
||||
else {
|
||||
@@ -1154,7 +1134,7 @@ const _super = (function (geti, seti) {
|
||||
write("{}");
|
||||
}
|
||||
else {
|
||||
const indentedFlag = node.emitFlags & NodeEmitFlags.Indented;
|
||||
const indentedFlag = getEmitFlags(node) & EmitFlags.Indented;
|
||||
if (indentedFlag) {
|
||||
increaseIndent();
|
||||
}
|
||||
@@ -1170,24 +1150,22 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
function emitPropertyAccessExpression(node: PropertyAccessExpression) {
|
||||
if (tryEmitConstantValue(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let indentBeforeDot = false;
|
||||
let indentAfterDot = false;
|
||||
if (!(node.emitFlags & NodeEmitFlags.NoIndentation)) {
|
||||
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
|
||||
const dotRangeStart = node.expression.end;
|
||||
const dotRangeEnd = skipTrivia(currentText, node.expression.end) + 1;
|
||||
const dotToken = <Node>{ kind: SyntaxKind.DotToken, pos: dotRangeStart, end: dotRangeEnd };
|
||||
indentBeforeDot = needsIndentation(node, node.expression, dotToken);
|
||||
indentAfterDot = needsIndentation(node, dotToken, node.name);
|
||||
}
|
||||
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression);
|
||||
|
||||
emitExpression(node.expression);
|
||||
increaseIndentIf(indentBeforeDot);
|
||||
|
||||
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression);
|
||||
write(shouldEmitDotDot ? ".." : ".");
|
||||
|
||||
increaseIndentIf(indentAfterDot);
|
||||
emit(node.name);
|
||||
decreaseIndentIf(indentBeforeDot, indentAfterDot);
|
||||
@@ -1201,19 +1179,17 @@ const _super = (function (geti, seti) {
|
||||
const text = getLiteralTextOfNode(<LiteralExpression>expression);
|
||||
return text.indexOf(tokenToString(SyntaxKind.DotToken)) < 0;
|
||||
}
|
||||
else {
|
||||
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
|
||||
// check if constant enum value is integer
|
||||
const constantValue = tryGetConstEnumValue(expression);
|
||||
const constantValue = getConstantValue(expression);
|
||||
// isFinite handles cases when constantValue is undefined
|
||||
return isFinite(constantValue) && Math.floor(constantValue) === constantValue;
|
||||
return isFinite(constantValue)
|
||||
&& Math.floor(constantValue) === constantValue
|
||||
&& compilerOptions.removeComments;
|
||||
}
|
||||
}
|
||||
|
||||
function emitElementAccessExpression(node: ElementAccessExpression) {
|
||||
if (tryEmitConstantValue(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitExpression(node.expression);
|
||||
write("[");
|
||||
emitExpression(node.argumentExpression);
|
||||
@@ -1419,7 +1395,7 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
function emitBlockStatements(node: Block) {
|
||||
if (node.emitFlags & NodeEmitFlags.SingleLine) {
|
||||
if (getEmitFlags(node) & EmitFlags.SingleLine) {
|
||||
emitList(node, node.statements, ListFormat.SingleLineBlockStatements);
|
||||
}
|
||||
else {
|
||||
@@ -1623,12 +1599,12 @@ const _super = (function (geti, seti) {
|
||||
const body = node.body;
|
||||
if (body) {
|
||||
if (isBlock(body)) {
|
||||
const indentedFlag = node.emitFlags & NodeEmitFlags.Indented;
|
||||
const indentedFlag = getEmitFlags(node) & EmitFlags.Indented;
|
||||
if (indentedFlag) {
|
||||
increaseIndent();
|
||||
}
|
||||
|
||||
if (node.emitFlags & NodeEmitFlags.ReuseTempVariableScope) {
|
||||
if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) {
|
||||
emitSignatureHead(node);
|
||||
emitBlockFunctionBody(node, body);
|
||||
}
|
||||
@@ -1672,7 +1648,7 @@ const _super = (function (geti, seti) {
|
||||
// * A non-synthesized body's start and end position are on different lines.
|
||||
// * Any statement in the body starts on a new line.
|
||||
|
||||
if (body.emitFlags & NodeEmitFlags.SingleLine) {
|
||||
if (getEmitFlags(body) & EmitFlags.SingleLine) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1743,7 +1719,7 @@ const _super = (function (geti, seti) {
|
||||
write("class");
|
||||
emitNodeWithPrefix(" ", node.name, emitIdentifierName);
|
||||
|
||||
const indentedFlag = node.emitFlags & NodeEmitFlags.Indented;
|
||||
const indentedFlag = getEmitFlags(node) & EmitFlags.Indented;
|
||||
if (indentedFlag) {
|
||||
increaseIndent();
|
||||
}
|
||||
@@ -2076,8 +2052,8 @@ const _super = (function (geti, seti) {
|
||||
// "comment1" is not considered to be leading comment for node.initializer
|
||||
// but rather a trailing comment on the previous node.
|
||||
const initializer = node.initializer;
|
||||
if (!shouldSkipLeadingCommentsForNode(initializer)) {
|
||||
const commentRange = initializer.commentRange || initializer;
|
||||
if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) {
|
||||
const commentRange = getCommentRange(initializer);
|
||||
emitTrailingCommentsOfPosition(commentRange.pos);
|
||||
}
|
||||
|
||||
@@ -2149,23 +2125,23 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
function emitHelpers(node: Node) {
|
||||
const emitFlags = node.emitFlags;
|
||||
const emitFlags = getEmitFlags(node);
|
||||
let helpersEmitted = false;
|
||||
if (emitFlags & NodeEmitFlags.EmitEmitHelpers) {
|
||||
if (emitFlags & EmitFlags.EmitEmitHelpers) {
|
||||
helpersEmitted = emitEmitHelpers(currentSourceFile);
|
||||
}
|
||||
|
||||
if (emitFlags & NodeEmitFlags.EmitExportStar) {
|
||||
if (emitFlags & EmitFlags.EmitExportStar) {
|
||||
writeLines(exportStarHelper);
|
||||
helpersEmitted = true;
|
||||
}
|
||||
|
||||
if (emitFlags & NodeEmitFlags.EmitSuperHelper) {
|
||||
if (emitFlags & EmitFlags.EmitSuperHelper) {
|
||||
writeLines(superHelper);
|
||||
helpersEmitted = true;
|
||||
}
|
||||
|
||||
if (emitFlags & NodeEmitFlags.EmitAdvancedSuperHelper) {
|
||||
if (emitFlags & EmitFlags.EmitAdvancedSuperHelper) {
|
||||
writeLines(advancedSuperHelper);
|
||||
helpersEmitted = true;
|
||||
}
|
||||
@@ -2287,36 +2263,6 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
}
|
||||
|
||||
function tryEmitSubstitute(node: Node, emitNode: (node: Node) => void, isExpression: boolean) {
|
||||
if (isSubstitutionEnabled(node) && (node.emitFlags & NodeEmitFlags.NoSubstitution) === 0) {
|
||||
const substitute = onSubstituteNode(node, isExpression);
|
||||
if (substitute !== node) {
|
||||
substitute.emitFlags |= NodeEmitFlags.NoSubstitution;
|
||||
emitNode(substitute);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryEmitConstantValue(node: PropertyAccessExpression | ElementAccessExpression): boolean {
|
||||
const constantValue = tryGetConstEnumValue(node);
|
||||
if (constantValue !== undefined) {
|
||||
write(String(constantValue));
|
||||
if (!compilerOptions.removeComments) {
|
||||
const propertyName = isPropertyAccessExpression(node)
|
||||
? declarationNameToString(node.name)
|
||||
: getTextOfNode(node.argumentExpression);
|
||||
write(` /* ${propertyName} */`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function emitEmbeddedStatement(node: Statement) {
|
||||
if (isBlock(node)) {
|
||||
write(" ");
|
||||
@@ -2440,7 +2386,7 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
if (shouldEmitInterveningComments) {
|
||||
const commentRange = child.commentRange || child;
|
||||
const commentRange = getCommentRange(child);
|
||||
emitTrailingCommentsOfPosition(commentRange.pos);
|
||||
}
|
||||
else {
|
||||
@@ -2496,31 +2442,13 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
|
||||
function writeToken(token: SyntaxKind, pos: number, contextNode?: Node) {
|
||||
const tokenStartPos = emitTokenStart(token, pos, contextNode, shouldSkipLeadingSourceMapForToken, getTokenSourceMapRange);
|
||||
const tokenEndPos = writeTokenText(token, tokenStartPos);
|
||||
return emitTokenEnd(token, tokenEndPos, contextNode, shouldSkipTrailingSourceMapForToken, getTokenSourceMapRange);
|
||||
}
|
||||
|
||||
function shouldSkipLeadingSourceMapForToken(contextNode: Node) {
|
||||
return (contextNode.emitFlags & NodeEmitFlags.NoTokenLeadingSourceMaps) !== 0;
|
||||
}
|
||||
|
||||
function shouldSkipTrailingSourceMapForToken(contextNode: Node) {
|
||||
return (contextNode.emitFlags & NodeEmitFlags.NoTokenTrailingSourceMaps) !== 0;
|
||||
return emitTokenWithSourceMap(contextNode, token, pos, writeTokenText);
|
||||
}
|
||||
|
||||
function writeTokenText(token: SyntaxKind, pos?: number) {
|
||||
const tokenString = tokenToString(token);
|
||||
write(tokenString);
|
||||
return positionIsSynthesized(pos) ? -1 : pos + tokenString.length;
|
||||
}
|
||||
|
||||
function writeTokenNode(node: Node) {
|
||||
if (node) {
|
||||
emitStart(/*range*/ node, /*contextNode*/ node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange);
|
||||
writeTokenText(node.kind);
|
||||
emitEnd(/*range*/ node, /*contextNode*/ node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange);
|
||||
}
|
||||
return pos < 0 ? pos : pos + tokenString.length;
|
||||
}
|
||||
|
||||
function increaseIndentIf(value: boolean, valueToWriteWhenNotIndenting?: string) {
|
||||
@@ -2685,16 +2613,6 @@ const _super = (function (geti, seti) {
|
||||
return getLiteralText(node, currentSourceFile, languageVersion);
|
||||
}
|
||||
|
||||
function tryGetConstEnumValue(node: Node): number {
|
||||
if (compilerOptions.isolatedModules) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return isPropertyAccessExpression(node) || isElementAccessExpression(node)
|
||||
? resolver.getConstantValue(<PropertyAccessExpression | ElementAccessExpression>node)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function isSingleLineEmptyBlock(block: Block) {
|
||||
return !block.multiLine
|
||||
&& block.statements.length === 0
|
||||
|
||||
+175
-11
@@ -76,7 +76,7 @@ namespace ts {
|
||||
// the original node. We also need to exclude specific properties and only include own-
|
||||
// properties (to skip members already defined on the shared prototype).
|
||||
const clone = <T>createNode(node.kind, /*location*/ undefined, node.flags);
|
||||
clone.original = node;
|
||||
setOriginalNode(clone, node);
|
||||
|
||||
for (const key in node) {
|
||||
if (clone.hasOwnProperty(key) || !node.hasOwnProperty(key)) {
|
||||
@@ -435,7 +435,7 @@ namespace ts {
|
||||
export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange, flags?: NodeFlags) {
|
||||
const node = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, location, flags);
|
||||
node.expression = parenthesizeForAccess(expression);
|
||||
node.emitFlags = NodeEmitFlags.NoIndentation;
|
||||
(node.emitNode || (node.emitNode = {})).flags |= EmitFlags.NoIndentation;
|
||||
node.name = typeof name === "string" ? createIdentifier(name) : name;
|
||||
return node;
|
||||
}
|
||||
@@ -444,7 +444,7 @@ namespace ts {
|
||||
if (node.expression !== expression || node.name !== name) {
|
||||
const propertyAccess = createPropertyAccess(expression, name, /*location*/ node, node.flags);
|
||||
// Because we are updating existed propertyAccess we want to inherit its emitFlags instead of using default from createPropertyAccess
|
||||
propertyAccess.emitFlags = node.emitFlags;
|
||||
(propertyAccess.emitNode || (propertyAccess.emitNode = {})).flags = getEmitFlags(node);
|
||||
return updateNode(propertyAccess, node);
|
||||
}
|
||||
return node;
|
||||
@@ -1551,7 +1551,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
const expression = isIdentifier(memberName) ? createPropertyAccess(target, memberName, location) : createElementAccess(target, memberName, location);
|
||||
expression.emitFlags |= NodeEmitFlags.NoNestedSourceMaps;
|
||||
(expression.emitNode || (expression.emitNode = {})).flags |= EmitFlags.NoNestedSourceMaps;
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
@@ -1744,7 +1744,7 @@ namespace ts {
|
||||
);
|
||||
|
||||
// Mark this node as originally an async function
|
||||
generatorFunc.emitFlags |= NodeEmitFlags.AsyncFunctionBody;
|
||||
(generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody;
|
||||
|
||||
return createCall(
|
||||
createHelperName(externalHelpersModuleName, "__awaiter"),
|
||||
@@ -2222,7 +2222,7 @@ namespace ts {
|
||||
target.push(startOnNewLine(createStatement(createLiteral("use strict"))));
|
||||
foundUseStrict = true;
|
||||
}
|
||||
if (statement.emitFlags & NodeEmitFlags.CustomPrologue) {
|
||||
if (getEmitFlags(statement) & EmitFlags.CustomPrologue) {
|
||||
target.push(visitor ? visitNode(statement, visitor, isStatement) : statement);
|
||||
}
|
||||
else {
|
||||
@@ -2643,14 +2643,178 @@ namespace ts {
|
||||
export function setOriginalNode<T extends Node>(node: T, original: Node): T {
|
||||
node.original = original;
|
||||
if (original) {
|
||||
const { emitFlags, commentRange, sourceMapRange } = original;
|
||||
if (emitFlags) node.emitFlags = emitFlags;
|
||||
if (commentRange) node.commentRange = commentRange;
|
||||
if (sourceMapRange) node.sourceMapRange = sourceMapRange;
|
||||
const emitNode = original.emitNode;
|
||||
if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode) {
|
||||
const { flags, commentRange, sourceMapRange, tokenSourceMapRanges } = sourceEmitNode;
|
||||
if (!destEmitNode && (flags || commentRange || sourceMapRange || tokenSourceMapRanges)) destEmitNode = {};
|
||||
if (flags) destEmitNode.flags = flags;
|
||||
if (commentRange) destEmitNode.commentRange = commentRange;
|
||||
if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange;
|
||||
if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges);
|
||||
return destEmitNode;
|
||||
}
|
||||
|
||||
function mergeTokenSourceMapRanges(sourceRanges: Map<TextRange>, destRanges: Map<TextRange>) {
|
||||
if (!destRanges) destRanges = createMap<TextRange>();
|
||||
copyProperties(sourceRanges, destRanges);
|
||||
return destRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any EmitNode entries from parse-tree nodes.
|
||||
* @param sourceFile A source file.
|
||||
*/
|
||||
export function disposeEmitNodes(sourceFile: SourceFile) {
|
||||
// During transformation we may need to annotate a parse tree node with transient
|
||||
// transformation properties. As parse tree nodes live longer than transformation
|
||||
// nodes, we need to make sure we reclaim any memory allocated for custom ranges
|
||||
// from these nodes to ensure we do not hold onto entire subtrees just for position
|
||||
// information. We also need to reset these nodes to a pre-transformation state
|
||||
// for incremental parsing scenarios so that we do not impact later emit.
|
||||
sourceFile = getSourceFileOfNode(getParseTreeNode(sourceFile));
|
||||
const emitNode = sourceFile && sourceFile.emitNode;
|
||||
const annotatedNodes = emitNode && emitNode.annotatedNodes;
|
||||
if (annotatedNodes) {
|
||||
for (const node of annotatedNodes) {
|
||||
node.emitNode = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a node with the current transformation, initializing
|
||||
* various transient transformation properties.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
function getOrCreateEmitNode(node: Node) {
|
||||
if (!node.emitNode) {
|
||||
if (isParseTreeNode(node)) {
|
||||
// To avoid holding onto transformation artifacts, we keep track of any
|
||||
// parse tree node we are annotating. This allows us to clean them up after
|
||||
// all transformations have completed.
|
||||
if (node.kind === SyntaxKind.SourceFile) {
|
||||
return node.emitNode = { annotatedNodes: [node] };
|
||||
}
|
||||
|
||||
const sourceFile = getSourceFileOfNode(node);
|
||||
getOrCreateEmitNode(sourceFile).annotatedNodes.push(node);
|
||||
}
|
||||
|
||||
node.emitNode = {};
|
||||
}
|
||||
|
||||
return node.emitNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets flags that control emit behavior of a node.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
export function getEmitFlags(node: Node) {
|
||||
const emitNode = node.emitNode;
|
||||
return emitNode && emitNode.flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets flags that control emit behavior of a node.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param emitFlags The NodeEmitFlags for the node.
|
||||
*/
|
||||
export function setEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags) {
|
||||
getOrCreateEmitNode(node).flags = emitFlags;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom text range to use when emitting source maps.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param range The text range.
|
||||
*/
|
||||
export function setSourceMapRange<T extends Node>(node: T, range: TextRange) {
|
||||
getOrCreateEmitNode(node).sourceMapRange = range;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TextRange to use for source maps for a token of a node.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param token The token.
|
||||
* @param range The text range.
|
||||
*/
|
||||
export function setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: TextRange) {
|
||||
const emitNode = getOrCreateEmitNode(node);
|
||||
const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = createMap<TextRange>());
|
||||
tokenSourceMapRanges[token] = range;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom text range to use when emitting comments.
|
||||
*/
|
||||
export function setCommentRange<T extends Node>(node: T, range: TextRange) {
|
||||
getOrCreateEmitNode(node).commentRange = range;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom text range to use when emitting comments.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
export function getCommentRange(node: Node) {
|
||||
const emitNode = node.emitNode;
|
||||
return (emitNode && emitNode.commentRange) || node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom text range to use when emitting source maps.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
export function getSourceMapRange(node: Node) {
|
||||
const emitNode = node.emitNode;
|
||||
return (emitNode && emitNode.sourceMapRange) || node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TextRange to use for source maps for a token of a node.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param token The token.
|
||||
*/
|
||||
export function getTokenSourceMapRange(node: Node, token: SyntaxKind) {
|
||||
const emitNode = node.emitNode;
|
||||
const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges;
|
||||
return tokenSourceMapRanges && tokenSourceMapRanges[token];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constant value to emit for an expression.
|
||||
*/
|
||||
export function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression) {
|
||||
const emitNode = node.emitNode;
|
||||
return emitNode && emitNode.constantValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constant value to emit for an expression.
|
||||
*/
|
||||
export function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: number) {
|
||||
const emitNode = getOrCreateEmitNode(node);
|
||||
emitNode.constantValue = value;
|
||||
return node;
|
||||
}
|
||||
|
||||
export function setTextRange<T extends TextRange>(node: T, location: TextRange): T {
|
||||
if (location) {
|
||||
node.pos = location.pos;
|
||||
@@ -2692,7 +2856,7 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get the name of a target module from an import/export declaration as should be written in the emitted output.
|
||||
* The emitted output name can be different from the input if:
|
||||
* 1. The module has a /// <amd-module name="<new name>" />
|
||||
|
||||
@@ -0,0 +1,744 @@
|
||||
/// <reference path="core.ts" />
|
||||
/// <reference path="diagnosticInformationMap.generated.ts" />
|
||||
|
||||
namespace ts {
|
||||
|
||||
/* @internal */
|
||||
export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void;
|
||||
export function trace(host: ModuleResolutionHost, message: DiagnosticMessage): void {
|
||||
host.trace(formatMessage.apply(undefined, arguments));
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean {
|
||||
return compilerOptions.traceResolution && host.trace !== undefined;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
|
||||
return { resolvedModule: resolvedFileName ? { resolvedFileName, isExternalLibraryImport } : undefined, failedLookupLocations };
|
||||
}
|
||||
|
||||
function moduleHasNonRelativeName(moduleName: string): boolean {
|
||||
return !(isRootedDiskPath(moduleName) || isExternalModuleNameRelative(moduleName));
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface ModuleResolutionState {
|
||||
host: ModuleResolutionHost;
|
||||
compilerOptions: CompilerOptions;
|
||||
traceEnabled: boolean;
|
||||
// skip .tsx files if jsx is not enabled
|
||||
skipTsx: boolean;
|
||||
}
|
||||
|
||||
function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string {
|
||||
const jsonContent = readJson(packageJsonPath, state.host);
|
||||
|
||||
function tryReadFromField(fieldName: string) {
|
||||
if (hasProperty(jsonContent, fieldName)) {
|
||||
const typesFile = (<any>jsonContent)[fieldName];
|
||||
if (typeof typesFile === "string") {
|
||||
const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile));
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath);
|
||||
}
|
||||
return typesFilePath;
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof typesFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const typesFilePath = tryReadFromField("typings") || tryReadFromField("types");
|
||||
if (typesFilePath) {
|
||||
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") {
|
||||
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);
|
||||
}
|
||||
const mainFilePath = normalizePath(combinePaths(baseDirectory, jsonContent.main));
|
||||
return mainFilePath;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readJson(path: string, host: ModuleResolutionHost): { typings?: string, types?: string, main?: string } {
|
||||
try {
|
||||
const jsonText = host.readFile(path);
|
||||
return jsonText ? JSON.parse(jsonText) : {};
|
||||
}
|
||||
catch (e) {
|
||||
// gracefully handle if readFile fails or returns not JSON
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const typeReferenceExtensions = [".d.ts"];
|
||||
|
||||
export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined {
|
||||
if (options.typeRoots) {
|
||||
return options.typeRoots;
|
||||
}
|
||||
|
||||
let currentDirectory: string;
|
||||
if (options.configFilePath) {
|
||||
currentDirectory = getDirectoryPath(options.configFilePath);
|
||||
}
|
||||
else if (host.getCurrentDirectory) {
|
||||
currentDirectory = host.getCurrentDirectory();
|
||||
}
|
||||
|
||||
return currentDirectory && getDefaultTypeRoots(currentDirectory, host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to every node_modules/@types directory from some ancestor directory.
|
||||
* Returns undefined if there are none.
|
||||
*/
|
||||
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
|
||||
if (!host.directoryExists) {
|
||||
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
|
||||
// And if it doesn't exist, tough.
|
||||
}
|
||||
|
||||
let typeRoots: string[];
|
||||
|
||||
while (true) {
|
||||
const atTypes = combinePaths(currentDirectory, nodeModulesAtTypes);
|
||||
if (host.directoryExists(atTypes)) {
|
||||
(typeRoots || (typeRoots = [])).push(atTypes);
|
||||
}
|
||||
|
||||
const parent = getDirectoryPath(currentDirectory);
|
||||
if (parent === currentDirectory) {
|
||||
break;
|
||||
}
|
||||
currentDirectory = parent;
|
||||
}
|
||||
|
||||
return typeRoots;
|
||||
}
|
||||
const nodeModulesAtTypes = combinePaths("node_modules", "@types");
|
||||
|
||||
/**
|
||||
* @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
|
||||
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
|
||||
* is assumed to be the same as root directory of the project.
|
||||
*/
|
||||
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
|
||||
const traceEnabled = isTraceEnabled(options, host);
|
||||
const moduleResolutionState: ModuleResolutionState = {
|
||||
compilerOptions: options,
|
||||
host: host,
|
||||
skipTsx: true,
|
||||
traceEnabled
|
||||
};
|
||||
|
||||
const typeRoots = getEffectiveTypeRoots(options, host);
|
||||
if (traceEnabled) {
|
||||
if (containingFile === undefined) {
|
||||
if (typeRoots === undefined) {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (typeRoots === undefined) {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
|
||||
// Check primary library paths
|
||||
if (typeRoots && typeRoots.length) {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", "));
|
||||
}
|
||||
const primarySearchPaths = typeRoots;
|
||||
for (const typeRoot of primarySearchPaths) {
|
||||
const candidate = combinePaths(typeRoot, typeReferenceDirectiveName);
|
||||
const candidateDirectory = getDirectoryPath(candidate);
|
||||
const resolvedFile = loadNodeModuleFromDirectory(typeReferenceExtensions, candidate, failedLookupLocations,
|
||||
!directoryProbablyExists(candidateDirectory, host), moduleResolutionState);
|
||||
|
||||
if (resolvedFile) {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, true);
|
||||
}
|
||||
return {
|
||||
resolvedTypeReferenceDirective: { primary: true, resolvedFileName: resolvedFile },
|
||||
failedLookupLocations
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths);
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedFile: string;
|
||||
let initialLocationForSecondaryLookup: string;
|
||||
if (containingFile) {
|
||||
initialLocationForSecondaryLookup = getDirectoryPath(containingFile);
|
||||
}
|
||||
|
||||
if (initialLocationForSecondaryLookup !== undefined) {
|
||||
// check secondary locations
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
|
||||
}
|
||||
resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState, /*checkOneLevel*/ false);
|
||||
if (traceEnabled) {
|
||||
if (resolvedFile) {
|
||||
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, false);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder);
|
||||
}
|
||||
}
|
||||
return {
|
||||
resolvedTypeReferenceDirective: resolvedFile
|
||||
? { primary: false, resolvedFileName: resolvedFile }
|
||||
: undefined,
|
||||
failedLookupLocations
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of options, returns the set of type directive names
|
||||
* that should be included for this program automatically.
|
||||
* This list could either come from the config file,
|
||||
* or from enumerating the types root + initial secondary types lookup location.
|
||||
* More type directives might appear in the program later as a result of loading actual source files;
|
||||
* this list is only the set of defaults that are implicitly included.
|
||||
*/
|
||||
export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] {
|
||||
// Use explicit type list from tsconfig.json
|
||||
if (options.types) {
|
||||
return options.types;
|
||||
}
|
||||
|
||||
// Walk the primary type lookup locations
|
||||
const result: string[] = [];
|
||||
if (host.directoryExists && host.getDirectories) {
|
||||
const typeRoots = getEffectiveTypeRoots(options, host);
|
||||
if (typeRoots) {
|
||||
for (const root of typeRoots) {
|
||||
if (host.directoryExists(root)) {
|
||||
for (const typeDirectivePath of host.getDirectories(root)) {
|
||||
const normalized = normalizePath(typeDirectivePath);
|
||||
const packageJsonPath = pathToPackageJson(combinePaths(root, normalized));
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
const isNotNeededPackage = host.fileExists(packageJsonPath) && readJson(packageJsonPath, host).typings === null;
|
||||
if (!isNotNeededPackage) {
|
||||
// Return just the type directive names
|
||||
result.push(getBaseFileName(normalized));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile);
|
||||
}
|
||||
|
||||
let moduleResolution = compilerOptions.moduleResolution;
|
||||
if (moduleResolution === undefined) {
|
||||
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]);
|
||||
}
|
||||
}
|
||||
|
||||
let result: ResolvedModuleWithFailedLookupLocations;
|
||||
switch (moduleResolution) {
|
||||
case ModuleResolutionKind.NodeJs:
|
||||
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host);
|
||||
break;
|
||||
case ModuleResolutionKind.Classic:
|
||||
result = classicNameResolver(moduleName, containingFile, compilerOptions, host);
|
||||
break;
|
||||
}
|
||||
|
||||
if (traceEnabled) {
|
||||
if (result.resolvedModule) {
|
||||
trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Every module resolution kind can has its specific understanding how to load module from a specific path on disk
|
||||
* I.e. for path '/a/b/c':
|
||||
* - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails
|
||||
* it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with
|
||||
* 'typings' entry or file 'index' with some supported extension
|
||||
* - Classic loader will only try to interpret '/a/b/c' as file.
|
||||
*/
|
||||
type ResolutionKindSpecificLoader = (candidate: string, extensions: string[], failedLookupLocations: string[], onlyRecordFailures: boolean, state: ModuleResolutionState) => string;
|
||||
|
||||
/**
|
||||
* Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to
|
||||
* mitigate differences between design time structure of the project and its runtime counterpart so the same import name
|
||||
* can be resolved successfully by TypeScript compiler and runtime module loader.
|
||||
* If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will
|
||||
* fallback to standard resolution routine.
|
||||
*
|
||||
* - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative
|
||||
* names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will
|
||||
* be '/a/b/c/d'
|
||||
* - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names
|
||||
* will be resolved based on the content of the module name.
|
||||
* Structure of 'paths' compiler options
|
||||
* 'paths': {
|
||||
* pattern-1: [...substitutions],
|
||||
* pattern-2: [...substitutions],
|
||||
* ...
|
||||
* pattern-n: [...substitutions]
|
||||
* }
|
||||
* Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against
|
||||
* all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case.
|
||||
* If pattern contains '*' then to match pattern "<prefix>*<suffix>" module name must start with the <prefix> and end with <suffix>.
|
||||
* <MatchedStar> denotes part of the module name between <prefix> and <suffix>.
|
||||
* If module name can be matches with multiple patterns then pattern with the longest prefix will be picked.
|
||||
* After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module
|
||||
* from the candidate location.
|
||||
* Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every
|
||||
* substitution in the list and replace '*' with <MatchedStar> string. If candidate location is not rooted it
|
||||
* will be converted to absolute using baseUrl.
|
||||
* For example:
|
||||
* baseUrl: /a/b/c
|
||||
* "paths": {
|
||||
* // match all module names
|
||||
* "*": [
|
||||
* "*", // use matched name as is,
|
||||
* // <matched name> will be looked as /a/b/c/<matched name>
|
||||
*
|
||||
* "folder1/*" // substitution will convert matched name to 'folder1/<matched name>',
|
||||
* // since it is not rooted then final candidate location will be /a/b/c/folder1/<matched name>
|
||||
* ],
|
||||
* // match module names that start with 'components/'
|
||||
* "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/<matched name> to '/root/components/folder1/<matched name>',
|
||||
* // it is rooted so it will be final candidate location
|
||||
* }
|
||||
*
|
||||
* 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if
|
||||
* they were in the same location. For example lets say there are two files
|
||||
* '/local/src/content/file1.ts'
|
||||
* '/shared/components/contracts/src/content/protocols/file2.ts'
|
||||
* After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so
|
||||
* if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime.
|
||||
* 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all
|
||||
* root dirs were merged together.
|
||||
* I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ].
|
||||
* Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file:
|
||||
* '/local/src/content/protocols/file2' and try to load it - failure.
|
||||
* Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will
|
||||
* be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining
|
||||
* entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location.
|
||||
*/
|
||||
function tryLoadModuleUsingOptionalResolutionSettings(moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
|
||||
failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string {
|
||||
|
||||
if (moduleHasNonRelativeName(moduleName)) {
|
||||
return tryLoadModuleUsingBaseUrl(moduleName, loader, failedLookupLocations, supportedExtensions, state);
|
||||
}
|
||||
else {
|
||||
return tryLoadModuleUsingRootDirs(moduleName, containingDirectory, loader, failedLookupLocations, supportedExtensions, state);
|
||||
}
|
||||
}
|
||||
|
||||
function tryLoadModuleUsingRootDirs(moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
|
||||
failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string {
|
||||
|
||||
if (!state.compilerOptions.rootDirs) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName);
|
||||
}
|
||||
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
|
||||
let matchedRootDir: string;
|
||||
let matchedNormalizedPrefix: string;
|
||||
for (const rootDir of state.compilerOptions.rootDirs) {
|
||||
// rootDirs are expected to be absolute
|
||||
// in case of tsconfig.json this will happen automatically - compiler will expand relative names
|
||||
// using location of tsconfig.json as base location
|
||||
let normalizedRoot = normalizePath(rootDir);
|
||||
if (!endsWith(normalizedRoot, directorySeparator)) {
|
||||
normalizedRoot += directorySeparator;
|
||||
}
|
||||
const isLongestMatchingPrefix =
|
||||
startsWith(candidate, normalizedRoot) &&
|
||||
(matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length);
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix);
|
||||
}
|
||||
|
||||
if (isLongestMatchingPrefix) {
|
||||
matchedNormalizedPrefix = normalizedRoot;
|
||||
matchedRootDir = rootDir;
|
||||
}
|
||||
}
|
||||
if (matchedNormalizedPrefix) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix);
|
||||
}
|
||||
const suffix = candidate.substr(matchedNormalizedPrefix.length);
|
||||
|
||||
// first - try to load from a initial location
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate);
|
||||
}
|
||||
const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(containingDirectory, state.host), state);
|
||||
if (resolvedFileName) {
|
||||
return resolvedFileName;
|
||||
}
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs);
|
||||
}
|
||||
// then try to resolve using remaining entries in rootDirs
|
||||
for (const rootDir of state.compilerOptions.rootDirs) {
|
||||
if (rootDir === matchedRootDir) {
|
||||
// skip the initially matched entry
|
||||
continue;
|
||||
}
|
||||
const candidate = combinePaths(normalizePath(rootDir), suffix);
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate);
|
||||
}
|
||||
const baseDirectory = getDirectoryPath(candidate);
|
||||
const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(baseDirectory, state.host), state);
|
||||
if (resolvedFileName) {
|
||||
return resolvedFileName;
|
||||
}
|
||||
}
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function tryLoadModuleUsingBaseUrl(moduleName: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: string[],
|
||||
supportedExtensions: string[], state: ModuleResolutionState): string {
|
||||
|
||||
if (!state.compilerOptions.baseUrl) {
|
||||
return undefined;
|
||||
}
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName);
|
||||
}
|
||||
|
||||
// string is for exact match
|
||||
let matchedPattern: Pattern | string | undefined = undefined;
|
||||
if (state.compilerOptions.paths) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
|
||||
}
|
||||
matchedPattern = matchPatternOrExact(getOwnKeys(state.compilerOptions.paths), moduleName);
|
||||
}
|
||||
|
||||
if (matchedPattern) {
|
||||
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
|
||||
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
|
||||
}
|
||||
for (const subst of state.compilerOptions.paths[matchedPatternText]) {
|
||||
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
|
||||
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, path));
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
|
||||
}
|
||||
const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
|
||||
if (resolvedFileName) {
|
||||
return resolvedFileName;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, moduleName));
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, state.compilerOptions.baseUrl, candidate);
|
||||
}
|
||||
|
||||
return loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
|
||||
}
|
||||
}
|
||||
|
||||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const containingDirectory = getDirectoryPath(containingFile);
|
||||
const supportedExtensions = getSupportedExtensions(compilerOptions);
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
const state = { compilerOptions, host, traceEnabled, skipTsx: false };
|
||||
let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName,
|
||||
failedLookupLocations, supportedExtensions, state);
|
||||
|
||||
let isExternalLibraryImport = false;
|
||||
if (!resolvedFileName) {
|
||||
if (moduleHasNonRelativeName(moduleName)) {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName);
|
||||
}
|
||||
resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state, /*checkOneLevel*/ false);
|
||||
isExternalLibraryImport = resolvedFileName !== undefined;
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
resolvedFileName = nodeLoadModuleByRelativeName(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedFileName && host.realpath) {
|
||||
const originalFileName = resolvedFileName;
|
||||
resolvedFileName = normalizePath(host.realpath(resolvedFileName));
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_real_path_for_0_result_1, originalFileName, resolvedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
return createResolvedModule(resolvedFileName, isExternalLibraryImport, failedLookupLocations);
|
||||
}
|
||||
|
||||
function nodeLoadModuleByRelativeName(candidate: string, supportedExtensions: string[], failedLookupLocations: string[],
|
||||
onlyRecordFailures: boolean, state: ModuleResolutionState): string {
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0, candidate);
|
||||
}
|
||||
|
||||
const resolvedFileName = !pathEndsWithDirectorySeparator(candidate) && loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, onlyRecordFailures, state);
|
||||
|
||||
return resolvedFileName || loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, onlyRecordFailures, state);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
|
||||
// if host does not support 'directoryExists' assume that directory will exist
|
||||
return !host.directoryExists || host.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary
|
||||
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
|
||||
*/
|
||||
function loadModuleFromFile(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
|
||||
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
|
||||
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocation, onlyRecordFailures, state);
|
||||
if (resolvedByAddingExtension) {
|
||||
return resolvedByAddingExtension;
|
||||
}
|
||||
|
||||
// If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one;
|
||||
// e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts"
|
||||
if (hasJavaScriptFileExtension(candidate)) {
|
||||
const extensionless = removeFileExtension(candidate);
|
||||
if (state.traceEnabled) {
|
||||
const extension = candidate.substring(extensionless.length);
|
||||
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
|
||||
}
|
||||
return tryAddingExtensions(extensionless, extensions, failedLookupLocation, onlyRecordFailures, state);
|
||||
}
|
||||
}
|
||||
|
||||
/** Try to return an existing file that adds one of the `extensions` to `candidate`. */
|
||||
function tryAddingExtensions(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
|
||||
if (!onlyRecordFailures) {
|
||||
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
|
||||
const directory = getDirectoryPath(candidate);
|
||||
if (directory) {
|
||||
onlyRecordFailures = !directoryProbablyExists(directory, state.host);
|
||||
}
|
||||
}
|
||||
return forEach(extensions, ext =>
|
||||
!(state.skipTsx && isJsxOrTsxExtension(ext)) && tryFile(candidate + ext, failedLookupLocation, onlyRecordFailures, state));
|
||||
}
|
||||
|
||||
/** Return the file if it exists. */
|
||||
function tryFile(fileName: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
|
||||
if (!onlyRecordFailures && state.host.fileExists(fileName)) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.File_0_does_not_exist, fileName);
|
||||
}
|
||||
failedLookupLocation.push(fileName);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string {
|
||||
const packageJsonPath = pathToPackageJson(candidate);
|
||||
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);
|
||||
if (typesFile) {
|
||||
const onlyRecordFailures = !directoryProbablyExists(getDirectoryPath(typesFile), state.host);
|
||||
// A package.json "typings" may specify an exact filename, or may choose to omit an extension.
|
||||
const result = tryFile(typesFile, failedLookupLocation, onlyRecordFailures, state) ||
|
||||
tryAddingExtensions(typesFile, extensions, failedLookupLocation, onlyRecordFailures, state);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.package_json_does_not_have_types_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.File_0_does_not_exist, packageJsonPath);
|
||||
}
|
||||
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
|
||||
failedLookupLocation.push(packageJsonPath);
|
||||
}
|
||||
|
||||
return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state);
|
||||
}
|
||||
|
||||
function pathToPackageJson(directory: string): string {
|
||||
return combinePaths(directory, "package.json");
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string {
|
||||
const nodeModulesFolder = combinePaths(directory, "node_modules");
|
||||
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
|
||||
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
|
||||
const supportedExtensions = getSupportedExtensions(state.compilerOptions);
|
||||
|
||||
let result = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, !nodeModulesFolderExists, state);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: 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)) {
|
||||
// Always prefer a TypeScript (.ts, .tsx, .d.ts) file shipped with the package
|
||||
return packageResult;
|
||||
}
|
||||
else {
|
||||
// Else prefer a types package over non-TypeScript results (e.g. JavaScript files)
|
||||
const typesResult = loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state);
|
||||
if (typesResult || packageResult) {
|
||||
return typesResult || packageResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parentPath = getDirectoryPath(directory);
|
||||
if (parentPath === directory || checkOneLevel) {
|
||||
break;
|
||||
}
|
||||
|
||||
directory = parentPath;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
const state = { compilerOptions, host, traceEnabled, skipTsx: !compilerOptions.jsx };
|
||||
const failedLookupLocations: string[] = [];
|
||||
const supportedExtensions = getSupportedExtensions(compilerOptions);
|
||||
let containingDirectory = getDirectoryPath(containingFile);
|
||||
|
||||
const resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, supportedExtensions, state);
|
||||
if (resolvedFileName) {
|
||||
return createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/false, failedLookupLocations);
|
||||
}
|
||||
|
||||
let referencedSourceFile: string;
|
||||
if (moduleHasNonRelativeName(moduleName)) {
|
||||
while (true) {
|
||||
const searchName = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
|
||||
if (referencedSourceFile) {
|
||||
break;
|
||||
}
|
||||
const parentPath = getDirectoryPath(containingDirectory);
|
||||
if (parentPath === containingDirectory) {
|
||||
break;
|
||||
}
|
||||
containingDirectory = parentPath;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
referencedSourceFile = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
|
||||
}
|
||||
|
||||
|
||||
return referencedSourceFile
|
||||
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
|
||||
: { resolvedModule: undefined, failedLookupLocations };
|
||||
}
|
||||
}
|
||||
+12
-825
@@ -4,6 +4,7 @@
|
||||
|
||||
namespace ts {
|
||||
/** The version of the TypeScript compiler release */
|
||||
|
||||
export const version = "2.1.0";
|
||||
|
||||
const emptyArray: any[] = [];
|
||||
@@ -74,788 +75,6 @@ namespace ts {
|
||||
return getNormalizedPathFromPathComponents(commonPathComponents);
|
||||
}
|
||||
|
||||
function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void;
|
||||
function trace(host: ModuleResolutionHost, message: DiagnosticMessage): void {
|
||||
host.trace(formatMessage.apply(undefined, arguments));
|
||||
}
|
||||
|
||||
function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean {
|
||||
return compilerOptions.traceResolution && host.trace !== undefined;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
let seenAsterisk = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) === CharacterCodes.asterisk) {
|
||||
if (!seenAsterisk) {
|
||||
seenAsterisk = true;
|
||||
}
|
||||
else {
|
||||
// have already seen asterisk
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function createResolvedModule(resolvedFileName: string, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
|
||||
return { resolvedModule: resolvedFileName ? { resolvedFileName, isExternalLibraryImport } : undefined, failedLookupLocations };
|
||||
}
|
||||
|
||||
function moduleHasNonRelativeName(moduleName: string): boolean {
|
||||
return !(isRootedDiskPath(moduleName) || isExternalModuleNameRelative(moduleName));
|
||||
}
|
||||
|
||||
interface ModuleResolutionState {
|
||||
host: ModuleResolutionHost;
|
||||
compilerOptions: CompilerOptions;
|
||||
traceEnabled: boolean;
|
||||
// skip .tsx files if jsx is not enabled
|
||||
skipTsx: boolean;
|
||||
}
|
||||
|
||||
function tryReadTypesSection(packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string {
|
||||
const jsonContent = readJson(packageJsonPath, state.host);
|
||||
|
||||
function tryReadFromField(fieldName: string) {
|
||||
if (hasProperty(jsonContent, fieldName)) {
|
||||
const typesFile = (<any>jsonContent)[fieldName];
|
||||
if (typeof typesFile === "string") {
|
||||
const typesFilePath = normalizePath(combinePaths(baseDirectory, typesFile));
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, typesFile, typesFilePath);
|
||||
}
|
||||
return typesFilePath;
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof typesFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const typesFilePath = tryReadFromField("typings") || tryReadFromField("types");
|
||||
if (typesFilePath) {
|
||||
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") {
|
||||
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);
|
||||
}
|
||||
const mainFilePath = normalizePath(combinePaths(baseDirectory, jsonContent.main));
|
||||
return mainFilePath;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readJson(path: string, host: ModuleResolutionHost): { typings?: string, types?: string, main?: string } {
|
||||
try {
|
||||
const jsonText = host.readFile(path);
|
||||
return jsonText ? JSON.parse(jsonText) : {};
|
||||
}
|
||||
catch (e) {
|
||||
// gracefully handle if readFile fails or returns not JSON
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const typeReferenceExtensions = [".d.ts"];
|
||||
|
||||
export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined {
|
||||
if (options.typeRoots) {
|
||||
return options.typeRoots;
|
||||
}
|
||||
|
||||
let currentDirectory: string;
|
||||
if (options.configFilePath) {
|
||||
currentDirectory = getDirectoryPath(options.configFilePath);
|
||||
}
|
||||
else if (host.getCurrentDirectory) {
|
||||
currentDirectory = host.getCurrentDirectory();
|
||||
}
|
||||
|
||||
return currentDirectory && getDefaultTypeRoots(currentDirectory, host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to every node_modules/@types directory from some ancestor directory.
|
||||
* Returns undefined if there are none.
|
||||
*/
|
||||
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
|
||||
if (!host.directoryExists) {
|
||||
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
|
||||
// And if it doesn't exist, tough.
|
||||
}
|
||||
|
||||
let typeRoots: string[];
|
||||
|
||||
while (true) {
|
||||
const atTypes = combinePaths(currentDirectory, nodeModulesAtTypes);
|
||||
if (host.directoryExists(atTypes)) {
|
||||
(typeRoots || (typeRoots = [])).push(atTypes);
|
||||
}
|
||||
|
||||
const parent = getDirectoryPath(currentDirectory);
|
||||
if (parent === currentDirectory) {
|
||||
break;
|
||||
}
|
||||
currentDirectory = parent;
|
||||
}
|
||||
|
||||
return typeRoots;
|
||||
}
|
||||
const nodeModulesAtTypes = combinePaths("node_modules", "@types");
|
||||
|
||||
/**
|
||||
* @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
|
||||
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
|
||||
* is assumed to be the same as root directory of the project.
|
||||
*/
|
||||
export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
|
||||
const traceEnabled = isTraceEnabled(options, host);
|
||||
const moduleResolutionState: ModuleResolutionState = {
|
||||
compilerOptions: options,
|
||||
host: host,
|
||||
skipTsx: true,
|
||||
traceEnabled
|
||||
};
|
||||
|
||||
const typeRoots = getEffectiveTypeRoots(options, host);
|
||||
if (traceEnabled) {
|
||||
if (containingFile === undefined) {
|
||||
if (typeRoots === undefined) {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (typeRoots === undefined) {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
|
||||
// Check primary library paths
|
||||
if (typeRoots && typeRoots.length) {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", "));
|
||||
}
|
||||
const primarySearchPaths = typeRoots;
|
||||
for (const typeRoot of primarySearchPaths) {
|
||||
const candidate = combinePaths(typeRoot, typeReferenceDirectiveName);
|
||||
const candidateDirectory = getDirectoryPath(candidate);
|
||||
const resolvedFile = loadNodeModuleFromDirectory(typeReferenceExtensions, candidate, failedLookupLocations,
|
||||
!directoryProbablyExists(candidateDirectory, host), moduleResolutionState);
|
||||
|
||||
if (resolvedFile) {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, true);
|
||||
}
|
||||
return {
|
||||
resolvedTypeReferenceDirective: { primary: true, resolvedFileName: resolvedFile },
|
||||
failedLookupLocations
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths);
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedFile: string;
|
||||
let initialLocationForSecondaryLookup: string;
|
||||
if (containingFile) {
|
||||
initialLocationForSecondaryLookup = getDirectoryPath(containingFile);
|
||||
}
|
||||
|
||||
if (initialLocationForSecondaryLookup !== undefined) {
|
||||
// check secondary locations
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup);
|
||||
}
|
||||
resolvedFile = loadModuleFromNodeModules(typeReferenceDirectiveName, initialLocationForSecondaryLookup, failedLookupLocations, moduleResolutionState);
|
||||
if (traceEnabled) {
|
||||
if (resolvedFile) {
|
||||
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFile, false);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder);
|
||||
}
|
||||
}
|
||||
return {
|
||||
resolvedTypeReferenceDirective: resolvedFile
|
||||
? { primary: false, resolvedFileName: resolvedFile }
|
||||
: undefined,
|
||||
failedLookupLocations
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile);
|
||||
}
|
||||
|
||||
let moduleResolution = compilerOptions.moduleResolution;
|
||||
if (moduleResolution === undefined) {
|
||||
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]);
|
||||
}
|
||||
}
|
||||
|
||||
let result: ResolvedModuleWithFailedLookupLocations;
|
||||
switch (moduleResolution) {
|
||||
case ModuleResolutionKind.NodeJs:
|
||||
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host);
|
||||
break;
|
||||
case ModuleResolutionKind.Classic:
|
||||
result = classicNameResolver(moduleName, containingFile, compilerOptions, host);
|
||||
break;
|
||||
}
|
||||
|
||||
if (traceEnabled) {
|
||||
if (result.resolvedModule) {
|
||||
trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName);
|
||||
}
|
||||
else {
|
||||
trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Every module resolution kind can has its specific understanding how to load module from a specific path on disk
|
||||
* I.e. for path '/a/b/c':
|
||||
* - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails
|
||||
* it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with
|
||||
* 'typings' entry or file 'index' with some supported extension
|
||||
* - Classic loader will only try to interpret '/a/b/c' as file.
|
||||
*/
|
||||
type ResolutionKindSpecificLoader = (candidate: string, extensions: string[], failedLookupLocations: string[], onlyRecordFailures: boolean, state: ModuleResolutionState) => string;
|
||||
|
||||
/**
|
||||
* Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to
|
||||
* mitigate differences between design time structure of the project and its runtime counterpart so the same import name
|
||||
* can be resolved successfully by TypeScript compiler and runtime module loader.
|
||||
* If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will
|
||||
* fallback to standard resolution routine.
|
||||
*
|
||||
* - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative
|
||||
* names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will
|
||||
* be '/a/b/c/d'
|
||||
* - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names
|
||||
* will be resolved based on the content of the module name.
|
||||
* Structure of 'paths' compiler options
|
||||
* 'paths': {
|
||||
* pattern-1: [...substitutions],
|
||||
* pattern-2: [...substitutions],
|
||||
* ...
|
||||
* pattern-n: [...substitutions]
|
||||
* }
|
||||
* Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against
|
||||
* all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case.
|
||||
* If pattern contains '*' then to match pattern "<prefix>*<suffix>" module name must start with the <prefix> and end with <suffix>.
|
||||
* <MatchedStar> denotes part of the module name between <prefix> and <suffix>.
|
||||
* If module name can be matches with multiple patterns then pattern with the longest prefix will be picked.
|
||||
* After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module
|
||||
* from the candidate location.
|
||||
* Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every
|
||||
* substitution in the list and replace '*' with <MatchedStar> string. If candidate location is not rooted it
|
||||
* will be converted to absolute using baseUrl.
|
||||
* For example:
|
||||
* baseUrl: /a/b/c
|
||||
* "paths": {
|
||||
* // match all module names
|
||||
* "*": [
|
||||
* "*", // use matched name as is,
|
||||
* // <matched name> will be looked as /a/b/c/<matched name>
|
||||
*
|
||||
* "folder1/*" // substitution will convert matched name to 'folder1/<matched name>',
|
||||
* // since it is not rooted then final candidate location will be /a/b/c/folder1/<matched name>
|
||||
* ],
|
||||
* // match module names that start with 'components/'
|
||||
* "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/<matched name> to '/root/components/folder1/<matched name>',
|
||||
* // it is rooted so it will be final candidate location
|
||||
* }
|
||||
*
|
||||
* 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if
|
||||
* they were in the same location. For example lets say there are two files
|
||||
* '/local/src/content/file1.ts'
|
||||
* '/shared/components/contracts/src/content/protocols/file2.ts'
|
||||
* After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so
|
||||
* if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime.
|
||||
* 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all
|
||||
* root dirs were merged together.
|
||||
* I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ].
|
||||
* Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file:
|
||||
* '/local/src/content/protocols/file2' and try to load it - failure.
|
||||
* Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will
|
||||
* be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining
|
||||
* entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location.
|
||||
*/
|
||||
function tryLoadModuleUsingOptionalResolutionSettings(moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
|
||||
failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string {
|
||||
|
||||
if (moduleHasNonRelativeName(moduleName)) {
|
||||
return tryLoadModuleUsingBaseUrl(moduleName, loader, failedLookupLocations, supportedExtensions, state);
|
||||
}
|
||||
else {
|
||||
return tryLoadModuleUsingRootDirs(moduleName, containingDirectory, loader, failedLookupLocations, supportedExtensions, state);
|
||||
}
|
||||
}
|
||||
|
||||
function tryLoadModuleUsingRootDirs(moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader,
|
||||
failedLookupLocations: string[], supportedExtensions: string[], state: ModuleResolutionState): string {
|
||||
|
||||
if (!state.compilerOptions.rootDirs) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName);
|
||||
}
|
||||
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
|
||||
let matchedRootDir: string;
|
||||
let matchedNormalizedPrefix: string;
|
||||
for (const rootDir of state.compilerOptions.rootDirs) {
|
||||
// rootDirs are expected to be absolute
|
||||
// in case of tsconfig.json this will happen automatically - compiler will expand relative names
|
||||
// using location of tsconfig.json as base location
|
||||
let normalizedRoot = normalizePath(rootDir);
|
||||
if (!endsWith(normalizedRoot, directorySeparator)) {
|
||||
normalizedRoot += directorySeparator;
|
||||
}
|
||||
const isLongestMatchingPrefix =
|
||||
startsWith(candidate, normalizedRoot) &&
|
||||
(matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length);
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix);
|
||||
}
|
||||
|
||||
if (isLongestMatchingPrefix) {
|
||||
matchedNormalizedPrefix = normalizedRoot;
|
||||
matchedRootDir = rootDir;
|
||||
}
|
||||
}
|
||||
if (matchedNormalizedPrefix) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix);
|
||||
}
|
||||
const suffix = candidate.substr(matchedNormalizedPrefix.length);
|
||||
|
||||
// first - try to load from a initial location
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate);
|
||||
}
|
||||
const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(containingDirectory, state.host), state);
|
||||
if (resolvedFileName) {
|
||||
return resolvedFileName;
|
||||
}
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs);
|
||||
}
|
||||
// then try to resolve using remaining entries in rootDirs
|
||||
for (const rootDir of state.compilerOptions.rootDirs) {
|
||||
if (rootDir === matchedRootDir) {
|
||||
// skip the initially matched entry
|
||||
continue;
|
||||
}
|
||||
const candidate = combinePaths(normalizePath(rootDir), suffix);
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate);
|
||||
}
|
||||
const baseDirectory = getDirectoryPath(candidate);
|
||||
const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(baseDirectory, state.host), state);
|
||||
if (resolvedFileName) {
|
||||
return resolvedFileName;
|
||||
}
|
||||
}
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function tryLoadModuleUsingBaseUrl(moduleName: string, loader: ResolutionKindSpecificLoader, failedLookupLocations: string[],
|
||||
supportedExtensions: string[], state: ModuleResolutionState): string {
|
||||
|
||||
if (!state.compilerOptions.baseUrl) {
|
||||
return undefined;
|
||||
}
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName);
|
||||
}
|
||||
|
||||
// string is for exact match
|
||||
let matchedPattern: Pattern | string | undefined = undefined;
|
||||
if (state.compilerOptions.paths) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
|
||||
}
|
||||
matchedPattern = matchPatternOrExact(getOwnKeys(state.compilerOptions.paths), moduleName);
|
||||
}
|
||||
|
||||
if (matchedPattern) {
|
||||
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
|
||||
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
|
||||
}
|
||||
for (const subst of state.compilerOptions.paths[matchedPatternText]) {
|
||||
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
|
||||
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, path));
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
|
||||
}
|
||||
const resolvedFileName = loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
|
||||
if (resolvedFileName) {
|
||||
return resolvedFileName;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, moduleName));
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, state.compilerOptions.baseUrl, candidate);
|
||||
}
|
||||
|
||||
return loader(candidate, supportedExtensions, failedLookupLocations, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* patternStrings contains both pattern strings (containing "*") and regular strings.
|
||||
* Return an exact match if possible, or a pattern match, or undefined.
|
||||
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
|
||||
*/
|
||||
function matchPatternOrExact(patternStrings: string[], candidate: string): string | Pattern | undefined {
|
||||
const patterns: Pattern[] = [];
|
||||
for (const patternString of patternStrings) {
|
||||
const pattern = tryParsePattern(patternString);
|
||||
if (pattern) {
|
||||
patterns.push(pattern);
|
||||
}
|
||||
else if (patternString === candidate) {
|
||||
// pattern was matched as is - no need to search further
|
||||
return patternString;
|
||||
}
|
||||
}
|
||||
|
||||
return findBestPatternMatch(patterns, _ => _, candidate);
|
||||
}
|
||||
|
||||
function patternText({prefix, suffix}: Pattern): string {
|
||||
return `${prefix}*${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that candidate matches pattern, returns the text matching the '*'.
|
||||
* E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar"
|
||||
*/
|
||||
function matchedText(pattern: Pattern, candidate: string): string {
|
||||
Debug.assert(isPatternMatch(pattern, candidate));
|
||||
return candidate.substr(pattern.prefix.length, candidate.length - pattern.suffix.length);
|
||||
}
|
||||
|
||||
/** Return the object corresponding to the best pattern to match `candidate`. */
|
||||
/* @internal */
|
||||
export function findBestPatternMatch<T>(values: T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined {
|
||||
let matchedValue: T | undefined = undefined;
|
||||
// use length of prefix as betterness criteria
|
||||
let longestMatchPrefixLength = -1;
|
||||
|
||||
for (const v of values) {
|
||||
const pattern = getPattern(v);
|
||||
if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) {
|
||||
longestMatchPrefixLength = pattern.prefix.length;
|
||||
matchedValue = v;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedValue;
|
||||
}
|
||||
|
||||
function isPatternMatch({prefix, suffix}: Pattern, candidate: string) {
|
||||
return candidate.length >= prefix.length + suffix.length &&
|
||||
startsWith(candidate, prefix) &&
|
||||
endsWith(candidate, suffix);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function tryParsePattern(pattern: string): Pattern | undefined {
|
||||
// This should be verified outside of here and a proper error thrown.
|
||||
Debug.assert(hasZeroOrOneAsteriskCharacter(pattern));
|
||||
const indexOfStar = pattern.indexOf("*");
|
||||
return indexOfStar === -1 ? undefined : {
|
||||
prefix: pattern.substr(0, indexOfStar),
|
||||
suffix: pattern.substr(indexOfStar + 1)
|
||||
};
|
||||
}
|
||||
|
||||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const containingDirectory = getDirectoryPath(containingFile);
|
||||
const supportedExtensions = getSupportedExtensions(compilerOptions);
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
|
||||
const failedLookupLocations: string[] = [];
|
||||
const state = { compilerOptions, host, traceEnabled, skipTsx: false };
|
||||
let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName,
|
||||
failedLookupLocations, supportedExtensions, state);
|
||||
|
||||
let isExternalLibraryImport = false;
|
||||
if (!resolvedFileName) {
|
||||
if (moduleHasNonRelativeName(moduleName)) {
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder, moduleName);
|
||||
}
|
||||
resolvedFileName = loadModuleFromNodeModules(moduleName, containingDirectory, failedLookupLocations, state);
|
||||
isExternalLibraryImport = resolvedFileName !== undefined;
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
resolvedFileName = nodeLoadModuleByRelativeName(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedFileName && host.realpath) {
|
||||
const originalFileName = resolvedFileName;
|
||||
resolvedFileName = normalizePath(host.realpath(resolvedFileName));
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Resolving_real_path_for_0_result_1, originalFileName, resolvedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
return createResolvedModule(resolvedFileName, isExternalLibraryImport, failedLookupLocations);
|
||||
}
|
||||
|
||||
function nodeLoadModuleByRelativeName(candidate: string, supportedExtensions: string[], failedLookupLocations: string[],
|
||||
onlyRecordFailures: boolean, state: ModuleResolutionState): string {
|
||||
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0, candidate);
|
||||
}
|
||||
|
||||
const resolvedFileName = !pathEndsWithDirectorySeparator(candidate) && loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, onlyRecordFailures, state);
|
||||
|
||||
return resolvedFileName || loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, onlyRecordFailures, state);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
|
||||
// if host does not support 'directoryExists' assume that directory will exist
|
||||
return !host.directoryExists || host.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary
|
||||
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
|
||||
*/
|
||||
function loadModuleFromFile(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
|
||||
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
|
||||
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocation, onlyRecordFailures, state);
|
||||
if (resolvedByAddingExtension) {
|
||||
return resolvedByAddingExtension;
|
||||
}
|
||||
|
||||
// If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one;
|
||||
// e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts"
|
||||
if (hasJavaScriptFileExtension(candidate)) {
|
||||
const extensionless = removeFileExtension(candidate);
|
||||
if (state.traceEnabled) {
|
||||
const extension = candidate.substring(extensionless.length);
|
||||
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
|
||||
}
|
||||
return tryAddingExtensions(extensionless, extensions, failedLookupLocation, onlyRecordFailures, state);
|
||||
}
|
||||
}
|
||||
|
||||
/** Try to return an existing file that adds one of the `extensions` to `candidate`. */
|
||||
function tryAddingExtensions(candidate: string, extensions: string[], failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
|
||||
if (!onlyRecordFailures) {
|
||||
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
|
||||
const directory = getDirectoryPath(candidate);
|
||||
if (directory) {
|
||||
onlyRecordFailures = !directoryProbablyExists(directory, state.host);
|
||||
}
|
||||
}
|
||||
return forEach(extensions, ext =>
|
||||
!(state.skipTsx && isJsxOrTsxExtension(ext)) && tryFile(candidate + ext, failedLookupLocation, onlyRecordFailures, state));
|
||||
}
|
||||
|
||||
/** Return the file if it exists. */
|
||||
function tryFile(fileName: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined {
|
||||
if (!onlyRecordFailures && state.host.fileExists(fileName)) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.File_0_does_not_exist, fileName);
|
||||
}
|
||||
failedLookupLocation.push(fileName);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function loadNodeModuleFromDirectory(extensions: string[], candidate: string, failedLookupLocation: string[], onlyRecordFailures: boolean, state: ModuleResolutionState): string {
|
||||
const packageJsonPath = pathToPackageJson(candidate);
|
||||
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);
|
||||
if (typesFile) {
|
||||
const onlyRecordFailures = !directoryProbablyExists(getDirectoryPath(typesFile), state.host);
|
||||
// A package.json "typings" may specify an exact filename, or may choose to omit an extension.
|
||||
const result = tryFile(typesFile, failedLookupLocation, onlyRecordFailures, state) ||
|
||||
tryAddingExtensions(typesFile, extensions, failedLookupLocation, onlyRecordFailures, state);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.package_json_does_not_have_types_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.File_0_does_not_exist, packageJsonPath);
|
||||
}
|
||||
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
|
||||
failedLookupLocation.push(packageJsonPath);
|
||||
}
|
||||
|
||||
return loadModuleFromFile(combinePaths(candidate, "index"), extensions, failedLookupLocation, !directoryExists, state);
|
||||
}
|
||||
|
||||
function pathToPackageJson(directory: string): string {
|
||||
return combinePaths(directory, "package.json");
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModulesFolder(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string {
|
||||
const nodeModulesFolder = combinePaths(directory, "node_modules");
|
||||
const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host);
|
||||
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
|
||||
const supportedExtensions = getSupportedExtensions(state.compilerOptions);
|
||||
|
||||
let result = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, !nodeModulesFolderExists, state);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
result = loadNodeModuleFromDirectory(supportedExtensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): 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)) {
|
||||
// Always prefer a TypeScript (.ts, .tsx, .d.ts) file shipped with the package
|
||||
return packageResult;
|
||||
}
|
||||
else {
|
||||
// Else prefer a types package over non-TypeScript results (e.g. JavaScript files)
|
||||
const typesResult = loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state);
|
||||
if (typesResult || packageResult) {
|
||||
return typesResult || packageResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parentPath = getDirectoryPath(directory);
|
||||
if (parentPath === directory) {
|
||||
break;
|
||||
}
|
||||
|
||||
directory = parentPath;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
const state = { compilerOptions, host, traceEnabled, skipTsx: !compilerOptions.jsx };
|
||||
const failedLookupLocations: string[] = [];
|
||||
const supportedExtensions = getSupportedExtensions(compilerOptions);
|
||||
let containingDirectory = getDirectoryPath(containingFile);
|
||||
|
||||
const resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, supportedExtensions, state);
|
||||
if (resolvedFileName) {
|
||||
return createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/false, failedLookupLocations);
|
||||
}
|
||||
|
||||
let referencedSourceFile: string;
|
||||
if (moduleHasNonRelativeName(moduleName)) {
|
||||
while (true) {
|
||||
const searchName = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
|
||||
if (referencedSourceFile) {
|
||||
break;
|
||||
}
|
||||
const parentPath = getDirectoryPath(containingDirectory);
|
||||
if (parentPath === containingDirectory) {
|
||||
break;
|
||||
}
|
||||
containingDirectory = parentPath;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
|
||||
referencedSourceFile = loadModuleFromFile(candidate, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
|
||||
}
|
||||
|
||||
|
||||
return referencedSourceFile
|
||||
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
|
||||
: { resolvedModule: undefined, failedLookupLocations };
|
||||
}
|
||||
|
||||
interface OutputFingerprint {
|
||||
hash: string;
|
||||
byteOrderMark: boolean;
|
||||
@@ -1070,44 +289,6 @@ namespace ts {
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of options, returns the set of type directive names
|
||||
* that should be included for this program automatically.
|
||||
* This list could either come from the config file,
|
||||
* or from enumerating the types root + initial secondary types lookup location.
|
||||
* More type directives might appear in the program later as a result of loading actual source files;
|
||||
* this list is only the set of defaults that are implicitly included.
|
||||
*/
|
||||
export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] {
|
||||
// Use explicit type list from tsconfig.json
|
||||
if (options.types) {
|
||||
return options.types;
|
||||
}
|
||||
|
||||
// Walk the primary type lookup locations
|
||||
const result: string[] = [];
|
||||
if (host.directoryExists && host.getDirectories) {
|
||||
const typeRoots = getEffectiveTypeRoots(options, host);
|
||||
if (typeRoots) {
|
||||
for (const root of typeRoots) {
|
||||
if (host.directoryExists(root)) {
|
||||
for (const typeDirectivePath of host.getDirectories(root)) {
|
||||
const normalized = normalizePath(typeDirectivePath);
|
||||
const packageJsonPath = pathToPackageJson(combinePaths(root, normalized));
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
const isNotNeededPackage = host.fileExists(packageJsonPath) && readJson(packageJsonPath, host).typings === null;
|
||||
if (!isNotNeededPackage) {
|
||||
// Return just the type directive names
|
||||
result.push(getBaseFileName(normalized));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program {
|
||||
let program: Program;
|
||||
let files: SourceFile[] = [];
|
||||
@@ -1230,7 +411,8 @@ namespace ts {
|
||||
getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(),
|
||||
getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(),
|
||||
getFileProcessingDiagnostics: () => fileProcessingDiagnostics,
|
||||
getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives
|
||||
getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives,
|
||||
dropDiagnosticsProducingTypeChecker
|
||||
};
|
||||
|
||||
verifyCompilerOptions();
|
||||
@@ -1426,19 +608,23 @@ namespace ts {
|
||||
return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true));
|
||||
}
|
||||
|
||||
function dropDiagnosticsProducingTypeChecker() {
|
||||
diagnosticsProducingTypeChecker = undefined;
|
||||
}
|
||||
|
||||
function getTypeChecker() {
|
||||
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
|
||||
}
|
||||
|
||||
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult {
|
||||
return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken));
|
||||
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult {
|
||||
return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles));
|
||||
}
|
||||
|
||||
function isEmitBlocked(emitFileName: string): boolean {
|
||||
return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName));
|
||||
}
|
||||
|
||||
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken): EmitResult {
|
||||
function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult {
|
||||
let declarationDiagnostics: Diagnostic[] = [];
|
||||
|
||||
if (options.noEmit) {
|
||||
@@ -1483,7 +669,8 @@ namespace ts {
|
||||
const emitResult = emitFiles(
|
||||
emitResolver,
|
||||
getEmitHost(writeFileCallback),
|
||||
sourceFile);
|
||||
sourceFile,
|
||||
emitOnlyDtsFiles);
|
||||
|
||||
performance.mark("afterEmit");
|
||||
performance.measure("Emit", "beforeEmit", "afterEmit");
|
||||
|
||||
+91
-455
@@ -18,11 +18,6 @@ namespace ts {
|
||||
*/
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* Gets test data for source maps.
|
||||
*/
|
||||
getSourceMapData(): SourceMapData;
|
||||
|
||||
/**
|
||||
* Set the current source file.
|
||||
*
|
||||
@@ -41,123 +36,23 @@ namespace ts {
|
||||
emitPos(pos: number): void;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the start of a range.
|
||||
* Emits a node with possible leading and trailing source maps.
|
||||
*
|
||||
* If the range's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
* @param emitContext The current emit context
|
||||
* @param node The node to emit.
|
||||
* @param emitCallback The callback used to emit the node.
|
||||
*/
|
||||
emitStart(range: TextRange): void;
|
||||
emitNodeWithSourceMap(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the start of a range.
|
||||
*
|
||||
* If the node's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
* @param contextNode The node for the current range.
|
||||
* @param ignoreNodeCallback A callback used to determine whether to skip source map
|
||||
* emit for the start position of this node.
|
||||
* @param ignoreChildrenCallback A callback used to determine whether to skip source
|
||||
* map emit for all children of this node.
|
||||
* @param getTextRangeCallbackCallback A callback used to get a custom source map
|
||||
* range for this node.
|
||||
*/
|
||||
emitStart(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallbackCallback: (node: Node) => TextRange): void;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the end of a range.
|
||||
*
|
||||
* If the range's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
*/
|
||||
emitEnd(range: TextRange): void;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the end of a range.
|
||||
*
|
||||
* If the node's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
* @param contextNode The node for the current range.
|
||||
* @param ignoreNodeCallback A callback used to determine whether to skip source map
|
||||
* emit for the end position of this node.
|
||||
* @param ignoreChildrenCallback A callback used to determine whether to skip source
|
||||
* map emit for all children of this node.
|
||||
* @param getTextRangeCallbackCallback A callback used to get a custom source map
|
||||
* range for this node.
|
||||
*/
|
||||
emitEnd(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallbackCallback: (node: Node) => TextRange): void;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the start position of a token.
|
||||
*
|
||||
* If the token's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
* Emits a token of a node node with possible leading and trailing source maps.
|
||||
*
|
||||
* @param node The node containing the token.
|
||||
* @param token The token to emit.
|
||||
* @param tokenStartPos The start position of the token.
|
||||
* @returns The start position of the token, following any trivia.
|
||||
* @param tokenStartPos The start pos of the token.
|
||||
* @param emitCallback The callback used to emit the token.
|
||||
*/
|
||||
emitTokenStart(token: SyntaxKind, tokenStartPos: number): number;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the start position of a token.
|
||||
*
|
||||
* If the token's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
*
|
||||
* @param token The token to emit.
|
||||
* @param tokenStartPos The start position of the token.
|
||||
* @param contextNode The node containing this token.
|
||||
* @param ignoreTokenCallback A callback used to determine whether to skip source map
|
||||
* emit for the start position of this token.
|
||||
* @param getTokenTextRangeCallback A callback used to get a custom source
|
||||
* map range for this node.
|
||||
* @returns The start position of the token, following any trivia.
|
||||
*/
|
||||
emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the end position of a token.
|
||||
*
|
||||
* If the token's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param token The token to emit.
|
||||
* @param tokenEndPos The end position of the token.
|
||||
* @returns The end position of the token.
|
||||
*/
|
||||
emitTokenEnd(token: SyntaxKind, tokenEndPos: number): number;
|
||||
|
||||
/**
|
||||
* Emits a mapping for the end position of a token.
|
||||
*
|
||||
* If the token's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param token The token to emit.
|
||||
* @param tokenEndPos The end position of the token.
|
||||
* @param contextNode The node containing this token.
|
||||
* @param ignoreTokenCallback A callback used to determine whether to skip source map
|
||||
* emit for the end position of this token.
|
||||
* @param getTokenTextRangeCallback A callback used to get a custom source
|
||||
* map range for this node.
|
||||
* @returns The end position of the token.
|
||||
*/
|
||||
emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number;
|
||||
|
||||
/*@deprecated*/ changeEmitSourcePos(): void;
|
||||
/*@deprecated*/ stopOverridingSpan(): void;
|
||||
emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenStartPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number): number;
|
||||
|
||||
/**
|
||||
* Gets the text for the source map.
|
||||
@@ -168,44 +63,11 @@ namespace ts {
|
||||
* Gets the SourceMappingURL for the source map.
|
||||
*/
|
||||
getSourceMappingURL(): string;
|
||||
}
|
||||
|
||||
export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
|
||||
const compilerOptions = host.getCompilerOptions();
|
||||
if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
|
||||
if (compilerOptions.extendedDiagnostics) {
|
||||
return createSourceMapWriterWithExtendedDiagnostics(host, writer);
|
||||
}
|
||||
|
||||
return createSourceMapWriterWorker(host, writer);
|
||||
}
|
||||
else {
|
||||
return getNullSourceMapWriter();
|
||||
}
|
||||
}
|
||||
|
||||
let nullSourceMapWriter: SourceMapWriter;
|
||||
|
||||
export function getNullSourceMapWriter(): SourceMapWriter {
|
||||
if (nullSourceMapWriter === undefined) {
|
||||
nullSourceMapWriter = {
|
||||
initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void { },
|
||||
reset(): void { },
|
||||
getSourceMapData(): SourceMapData { return undefined; },
|
||||
setSourceFile(sourceFile: SourceFile): void { },
|
||||
emitPos(pos: number): void { },
|
||||
emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { },
|
||||
emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { },
|
||||
emitTokenStart(token: SyntaxKind, pos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { return -1; },
|
||||
emitTokenEnd(token: SyntaxKind, end: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { return -1; },
|
||||
changeEmitSourcePos(): void { },
|
||||
stopOverridingSpan(): void { },
|
||||
getText(): string { return undefined; },
|
||||
getSourceMappingURL(): string { return undefined; }
|
||||
};
|
||||
}
|
||||
|
||||
return nullSourceMapWriter;
|
||||
/**
|
||||
* Gets test data for source maps.
|
||||
*/
|
||||
getSourceMapData(): SourceMapData;
|
||||
}
|
||||
|
||||
// Used for initialize lastEncodedSourceMapSpan and reset lastEncodedSourceMapSpan when updateLastEncodedAndRecordedSpans
|
||||
@@ -217,14 +79,12 @@ namespace ts {
|
||||
sourceIndex: 0
|
||||
};
|
||||
|
||||
function createSourceMapWriterWorker(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
|
||||
export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
|
||||
const compilerOptions = host.getCompilerOptions();
|
||||
const extendedDiagnostics = compilerOptions.extendedDiagnostics;
|
||||
let currentSourceFile: SourceFile;
|
||||
let currentSourceText: string;
|
||||
let sourceMapDir: string; // The directory in which sourcemap will be
|
||||
let stopOverridingSpan = false;
|
||||
let modifyLastSourcePos = false;
|
||||
|
||||
// Current source map file and its index in the sources list
|
||||
let sourceMapSourceIndex: number;
|
||||
@@ -236,13 +96,7 @@ namespace ts {
|
||||
|
||||
// Source map data
|
||||
let sourceMapData: SourceMapData;
|
||||
|
||||
// This keeps track of the number of times `disable` has been called without a
|
||||
// corresponding call to `enable`. As long as this value is non-zero, mappings will not
|
||||
// be recorded.
|
||||
// This is primarily used to provide a better experience when debugging binding
|
||||
// patterns and destructuring assignments for simple expressions.
|
||||
let disableDepth: number;
|
||||
let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap);
|
||||
|
||||
return {
|
||||
initialize,
|
||||
@@ -250,12 +104,8 @@ namespace ts {
|
||||
getSourceMapData: () => sourceMapData,
|
||||
setSourceFile,
|
||||
emitPos,
|
||||
emitStart,
|
||||
emitEnd,
|
||||
emitTokenStart,
|
||||
emitTokenEnd,
|
||||
changeEmitSourcePos,
|
||||
stopOverridingSpan: () => stopOverridingSpan = true,
|
||||
emitNodeWithSourceMap,
|
||||
emitTokenWithSourceMap,
|
||||
getText,
|
||||
getSourceMappingURL,
|
||||
};
|
||||
@@ -269,13 +119,16 @@ namespace ts {
|
||||
* @param isBundledEmit A value indicating whether the generated output file is a bundle.
|
||||
*/
|
||||
function initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceMapData) {
|
||||
reset();
|
||||
}
|
||||
|
||||
currentSourceFile = undefined;
|
||||
currentSourceText = undefined;
|
||||
disableDepth = 0;
|
||||
|
||||
// Current source map file and its index in the sources list
|
||||
sourceMapSourceIndex = -1;
|
||||
@@ -338,6 +191,10 @@ namespace ts {
|
||||
* Reset the SourceMapWriter to an empty state.
|
||||
*/
|
||||
function reset() {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSourceFile = undefined;
|
||||
sourceMapDir = undefined;
|
||||
sourceMapSourceIndex = undefined;
|
||||
@@ -345,64 +202,6 @@ namespace ts {
|
||||
lastEncodedSourceMapSpan = undefined;
|
||||
lastEncodedNameIndex = undefined;
|
||||
sourceMapData = undefined;
|
||||
disableDepth = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-enables the recording of mappings.
|
||||
*/
|
||||
function enable() {
|
||||
if (disableDepth > 0) {
|
||||
disableDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the recording of mappings.
|
||||
*/
|
||||
function disable() {
|
||||
disableDepth++;
|
||||
}
|
||||
|
||||
function updateLastEncodedAndRecordedSpans() {
|
||||
if (modifyLastSourcePos) {
|
||||
// Reset the source pos
|
||||
modifyLastSourcePos = false;
|
||||
|
||||
// Change Last recorded Map with last encoded emit line and character
|
||||
lastRecordedSourceMapSpan.emittedLine = lastEncodedSourceMapSpan.emittedLine;
|
||||
lastRecordedSourceMapSpan.emittedColumn = lastEncodedSourceMapSpan.emittedColumn;
|
||||
|
||||
// Pop sourceMapDecodedMappings to remove last entry
|
||||
sourceMapData.sourceMapDecodedMappings.pop();
|
||||
|
||||
// Point the lastEncodedSourceMapSpace to the previous encoded sourceMapSpan
|
||||
// If the list is empty which indicates that we are at the beginning of the file,
|
||||
// we have to reset it to default value (same value when we first initialize sourceMapWriter)
|
||||
lastEncodedSourceMapSpan = sourceMapData.sourceMapDecodedMappings.length ?
|
||||
sourceMapData.sourceMapDecodedMappings[sourceMapData.sourceMapDecodedMappings.length - 1] :
|
||||
defaultLastEncodedSourceMapSpan;
|
||||
|
||||
// TODO: Update lastEncodedNameIndex
|
||||
// Since we dont support this any more, lets not worry about it right now.
|
||||
// When we start supporting nameIndex, we will get back to this
|
||||
|
||||
// Change the encoded source map
|
||||
const sourceMapMappings = sourceMapData.sourceMapMappings;
|
||||
let lenthToSet = sourceMapMappings.length - 1;
|
||||
for (; lenthToSet >= 0; lenthToSet--) {
|
||||
const currentChar = sourceMapMappings.charAt(lenthToSet);
|
||||
if (currentChar === ",") {
|
||||
// Separator for the entry found
|
||||
break;
|
||||
}
|
||||
if (currentChar === ";" && lenthToSet !== 0 && sourceMapMappings.charAt(lenthToSet - 1) !== ";") {
|
||||
// Last line separator found
|
||||
break;
|
||||
}
|
||||
}
|
||||
sourceMapData.sourceMapMappings = sourceMapMappings.substr(0, Math.max(0, lenthToSet));
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding for sourcemap span
|
||||
@@ -459,7 +258,7 @@ namespace ts {
|
||||
* @param pos The position.
|
||||
*/
|
||||
function emitPos(pos: number) {
|
||||
if (positionIsSynthesized(pos) || disableDepth > 0) {
|
||||
if (disabled || positionIsSynthesized(pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -495,209 +294,89 @@ namespace ts {
|
||||
sourceColumn: sourceLinePos.character,
|
||||
sourceIndex: sourceMapSourceIndex
|
||||
};
|
||||
|
||||
stopOverridingSpan = false;
|
||||
}
|
||||
else if (!stopOverridingSpan) {
|
||||
else {
|
||||
// Take the new pos instead since there is no change in emittedLine and column since last location
|
||||
lastRecordedSourceMapSpan.sourceLine = sourceLinePos.line;
|
||||
lastRecordedSourceMapSpan.sourceColumn = sourceLinePos.character;
|
||||
lastRecordedSourceMapSpan.sourceIndex = sourceMapSourceIndex;
|
||||
}
|
||||
|
||||
updateLastEncodedAndRecordedSpans();
|
||||
|
||||
if (extendedDiagnostics) {
|
||||
performance.mark("afterSourcemap");
|
||||
performance.measure("Source Map", "beforeSourcemap", "afterSourcemap");
|
||||
}
|
||||
}
|
||||
|
||||
function getStartPosPastDecorators(range: TextRange) {
|
||||
const rangeHasDecorators = !!(range as Node).decorators;
|
||||
return skipTrivia(currentSourceText, rangeHasDecorators ? (range as Node).decorators.end : range.pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a mapping for the start of a range.
|
||||
* Emits a node with possible leading and trailing source maps.
|
||||
*
|
||||
* If the range's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
*
|
||||
* @param range The range to emit.0
|
||||
* @param node The node to emit.
|
||||
* @param emitCallback The callback used to emit the node.
|
||||
*/
|
||||
function emitStart(range: TextRange): void;
|
||||
/**
|
||||
* Emits a mapping for the start of a range.
|
||||
*
|
||||
* If the node's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
* @param contextNode The node for the current range.
|
||||
* @param ignoreNodeCallback A callback used to determine whether to skip source map
|
||||
* emit for the start position of this node.
|
||||
* @param ignoreChildrenCallback A callback used to determine whether to skip source
|
||||
* map emit for all children of this node.
|
||||
* @param getTextRangeCallbackCallback A callback used to get a custom source map
|
||||
* range for this node.
|
||||
*/
|
||||
function emitStart(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallback: (node: Node) => TextRange): void;
|
||||
function emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange) {
|
||||
if (contextNode) {
|
||||
if (!ignoreNodeCallback(contextNode)) {
|
||||
range = getTextRangeCallback(contextNode) || range;
|
||||
emitPos(getStartPosPastDecorators(range));
|
||||
}
|
||||
|
||||
if (ignoreChildrenCallback(contextNode)) {
|
||||
disable();
|
||||
}
|
||||
function emitNodeWithSourceMap(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) {
|
||||
if (disabled) {
|
||||
return emitCallback(emitContext, node);
|
||||
}
|
||||
else {
|
||||
emitPos(getStartPosPastDecorators(range));
|
||||
|
||||
if (node) {
|
||||
const emitNode = node.emitNode;
|
||||
const emitFlags = emitNode && emitNode.flags;
|
||||
const { pos, end } = emitNode && emitNode.sourceMapRange || node;
|
||||
|
||||
if (node.kind !== SyntaxKind.NotEmittedStatement
|
||||
&& (emitFlags & EmitFlags.NoLeadingSourceMap) === 0
|
||||
&& pos >= 0) {
|
||||
emitPos(skipTrivia(currentSourceText, pos));
|
||||
}
|
||||
|
||||
if (emitFlags & EmitFlags.NoNestedSourceMaps) {
|
||||
disabled = true;
|
||||
emitCallback(emitContext, node);
|
||||
disabled = false;
|
||||
}
|
||||
else {
|
||||
emitCallback(emitContext, node);
|
||||
}
|
||||
|
||||
if (node.kind !== SyntaxKind.NotEmittedStatement
|
||||
&& (emitFlags & EmitFlags.NoTrailingSourceMap) === 0
|
||||
&& end >= 0) {
|
||||
emitPos(end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a mapping for the end of a range.
|
||||
*
|
||||
* If the range's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
*/
|
||||
function emitEnd(range: TextRange): void;
|
||||
/**
|
||||
* Emits a mapping for the end of a range.
|
||||
*
|
||||
* If the node's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param range The range to emit.
|
||||
* @param contextNode The node for the current range.
|
||||
* @param ignoreNodeCallback A callback used to determine whether to skip source map
|
||||
* emit for the end position of this node.
|
||||
* @param ignoreChildrenCallback A callback used to determine whether to skip source
|
||||
* map emit for all children of this node.
|
||||
* @param getTextRangeCallbackCallback A callback used to get a custom source map
|
||||
* range for this node.
|
||||
*/
|
||||
function emitEnd(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean, ignoreChildrenCallback: (node: Node) => boolean, getTextRangeCallback: (node: Node) => TextRange): void;
|
||||
function emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange) {
|
||||
if (contextNode) {
|
||||
if (ignoreChildrenCallback(contextNode)) {
|
||||
enable();
|
||||
}
|
||||
|
||||
if (!ignoreNodeCallback(contextNode)) {
|
||||
range = getTextRangeCallback(contextNode) || range;
|
||||
emitPos(range.end);
|
||||
}
|
||||
}
|
||||
else {
|
||||
emitPos(range.end);
|
||||
}
|
||||
|
||||
stopOverridingSpan = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a mapping for the start position of a token.
|
||||
*
|
||||
* If the token's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
* Emits a token of a node with possible leading and trailing source maps.
|
||||
*
|
||||
* @param node The node containing the token.
|
||||
* @param token The token to emit.
|
||||
* @param tokenStartPos The start position of the token.
|
||||
* @returns The start position of the token, following any trivia.
|
||||
* @param tokenStartPos The start pos of the token.
|
||||
* @param emitCallback The callback used to emit the token.
|
||||
*/
|
||||
function emitTokenStart(token: SyntaxKind, tokenStartPos: number): number;
|
||||
/**
|
||||
* Emits a mapping for the start position of a token.
|
||||
*
|
||||
* If the token's start position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created. Any trivia at the start position in the original source will be
|
||||
* skipped.
|
||||
*
|
||||
* @param token The token to emit.
|
||||
* @param tokenStartPos The start position of the token.
|
||||
* @param contextNode The node containing this token.
|
||||
* @param ignoreTokenCallback A callback used to determine whether to skip source map
|
||||
* emit for the start position of this token.
|
||||
* @param getTokenTextRangeCallback A callback used to get a custom source
|
||||
* map range for this node.
|
||||
* @returns The start position of the token, following any trivia.
|
||||
*/
|
||||
function emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number;
|
||||
function emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number {
|
||||
if (contextNode) {
|
||||
if (ignoreTokenCallback(contextNode, token)) {
|
||||
return skipTrivia(currentSourceText, tokenStartPos);
|
||||
}
|
||||
|
||||
const range = getTokenTextRangeCallback(contextNode, token);
|
||||
if (range) {
|
||||
tokenStartPos = range.pos;
|
||||
}
|
||||
function emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number) {
|
||||
if (disabled) {
|
||||
return emitCallback(token, tokenPos);
|
||||
}
|
||||
|
||||
tokenStartPos = skipTrivia(currentSourceText, tokenStartPos);
|
||||
emitPos(tokenStartPos);
|
||||
return tokenStartPos;
|
||||
}
|
||||
const emitNode = node && node.emitNode;
|
||||
const emitFlags = emitNode && emitNode.flags;
|
||||
const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token];
|
||||
|
||||
/**
|
||||
* Emits a mapping for the end position of a token.
|
||||
*
|
||||
* If the token's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param token The token to emit.
|
||||
* @param tokenEndPos The end position of the token.
|
||||
* @returns The end position of the token.
|
||||
*/
|
||||
function emitTokenEnd(token: SyntaxKind, tokenEndPos: number): number;
|
||||
/**
|
||||
* Emits a mapping for the end position of a token.
|
||||
*
|
||||
* If the token's end position is synthetic (undefined or a negative value), no mapping
|
||||
* will be created.
|
||||
*
|
||||
* @param token The token to emit.
|
||||
* @param tokenEndPos The end position of the token.
|
||||
* @param contextNode The node containing this token.
|
||||
* @param ignoreTokenCallback A callback used to determine whether to skip source map
|
||||
* emit for the end position of this token.
|
||||
* @param getTokenTextRangeCallback A callback used to get a custom source
|
||||
* map range for this node.
|
||||
* @returns The end position of the token.
|
||||
*/
|
||||
function emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode: Node, ignoreTokenCallback: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback: (node: Node, token: SyntaxKind) => TextRange): number;
|
||||
function emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node, token: SyntaxKind) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number {
|
||||
if (contextNode) {
|
||||
if (ignoreTokenCallback(contextNode, token)) {
|
||||
return tokenEndPos;
|
||||
}
|
||||
|
||||
const range = getTokenTextRangeCallback(contextNode, token);
|
||||
if (range) {
|
||||
tokenEndPos = range.end;
|
||||
}
|
||||
tokenPos = skipTrivia(currentSourceText, range ? range.pos : tokenPos);
|
||||
if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) {
|
||||
emitPos(tokenPos);
|
||||
}
|
||||
|
||||
emitPos(tokenEndPos);
|
||||
return tokenEndPos;
|
||||
}
|
||||
tokenPos = emitCallback(token, tokenPos);
|
||||
|
||||
if (range) tokenPos = range.end;
|
||||
if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) {
|
||||
emitPos(tokenPos);
|
||||
}
|
||||
|
||||
// @deprecated
|
||||
function changeEmitSourcePos() {
|
||||
Debug.assert(!modifyLastSourcePos);
|
||||
modifyLastSourcePos = true;
|
||||
return tokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -706,6 +385,10 @@ namespace ts {
|
||||
* @param sourceFile The source file.
|
||||
*/
|
||||
function setSourceFile(sourceFile: SourceFile) {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSourceFile = sourceFile;
|
||||
currentSourceText = currentSourceFile.text;
|
||||
|
||||
@@ -738,6 +421,10 @@ namespace ts {
|
||||
* Gets the text for the source map.
|
||||
*/
|
||||
function getText() {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
encodeLastRecordedSourceMapSpan();
|
||||
|
||||
return stringify({
|
||||
@@ -755,6 +442,10 @@ namespace ts {
|
||||
* Gets the SourceMappingURL for the source map.
|
||||
*/
|
||||
function getSourceMappingURL() {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (compilerOptions.inlineSourceMap) {
|
||||
// Encode the sourceMap into the sourceMap url
|
||||
const base64SourceMapText = convertToBase64(getText());
|
||||
@@ -766,61 +457,6 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function createSourceMapWriterWithExtendedDiagnostics(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
|
||||
const {
|
||||
initialize,
|
||||
reset,
|
||||
getSourceMapData,
|
||||
setSourceFile,
|
||||
emitPos,
|
||||
emitStart,
|
||||
emitEnd,
|
||||
emitTokenStart,
|
||||
emitTokenEnd,
|
||||
changeEmitSourcePos,
|
||||
stopOverridingSpan,
|
||||
getText,
|
||||
getSourceMappingURL,
|
||||
} = createSourceMapWriterWorker(host, writer);
|
||||
return {
|
||||
initialize,
|
||||
reset,
|
||||
getSourceMapData,
|
||||
setSourceFile,
|
||||
emitPos(pos: number): void {
|
||||
performance.mark("sourcemapStart");
|
||||
emitPos(pos);
|
||||
performance.measure("sourceMapTime", "sourcemapStart");
|
||||
},
|
||||
emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void {
|
||||
performance.mark("emitSourcemap:emitStart");
|
||||
emitStart(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback);
|
||||
performance.measure("sourceMapTime", "emitSourcemap:emitStart");
|
||||
},
|
||||
emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void {
|
||||
performance.mark("emitSourcemap:emitEnd");
|
||||
emitEnd(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback);
|
||||
performance.measure("sourceMapTime", "emitSourcemap:emitEnd");
|
||||
},
|
||||
emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number {
|
||||
performance.mark("emitSourcemap:emitTokenStart");
|
||||
tokenStartPos = emitTokenStart(token, tokenStartPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback);
|
||||
performance.measure("sourceMapTime", "emitSourcemap:emitTokenStart");
|
||||
return tokenStartPos;
|
||||
},
|
||||
emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number {
|
||||
performance.mark("emitSourcemap:emitTokenEnd");
|
||||
tokenEndPos = emitTokenEnd(token, tokenEndPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback);
|
||||
performance.measure("sourceMapTime", "emitSourcemap:emitTokenEnd");
|
||||
return tokenEndPos;
|
||||
},
|
||||
changeEmitSourcePos,
|
||||
stopOverridingSpan,
|
||||
getText,
|
||||
getSourceMappingURL,
|
||||
};
|
||||
}
|
||||
|
||||
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
function base64FormatEncode(inValue: number) {
|
||||
|
||||
+26
-5
@@ -592,19 +592,40 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function recursiveCreateDirectory(directoryPath: string, sys: System) {
|
||||
const basePath = getDirectoryPath(directoryPath);
|
||||
const shouldCreateParent = directoryPath !== basePath && !sys.directoryExists(basePath);
|
||||
if (shouldCreateParent) {
|
||||
recursiveCreateDirectory(basePath, sys);
|
||||
}
|
||||
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
|
||||
sys.createDirectory(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
let sys: System;
|
||||
if (typeof ChakraHost !== "undefined") {
|
||||
return getChakraSystem();
|
||||
sys = getChakraSystem();
|
||||
}
|
||||
else if (typeof WScript !== "undefined" && typeof ActiveXObject === "function") {
|
||||
return getWScriptSystem();
|
||||
sys = getWScriptSystem();
|
||||
}
|
||||
else if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") {
|
||||
// process and process.nextTick checks if current environment is node-like
|
||||
// process.browser check excludes webpack and browserify
|
||||
return getNodeSystem();
|
||||
sys = getNodeSystem();
|
||||
}
|
||||
else {
|
||||
return undefined; // Unsupported host
|
||||
if (sys) {
|
||||
// patch writefile to create folder before writing the file
|
||||
const originalWriteFile = sys.writeFile;
|
||||
sys.writeFile = function(path, data, writeBom) {
|
||||
const directoryPath = getDirectoryPath(normalizeSlashes(path));
|
||||
if (directoryPath && !sys.directoryExists(directoryPath)) {
|
||||
recursiveCreateDirectory(directoryPath, sys);
|
||||
}
|
||||
originalWriteFile.call(sys, path, data, writeBom);
|
||||
};
|
||||
}
|
||||
return sys;
|
||||
})();
|
||||
}
|
||||
|
||||
+44
-334
@@ -29,46 +29,25 @@ namespace ts {
|
||||
/**
|
||||
* Gets the transformed source files.
|
||||
*/
|
||||
getSourceFiles(): SourceFile[];
|
||||
transformed: SourceFile[];
|
||||
|
||||
/**
|
||||
* Gets the TextRange to use for source maps for a token of a node.
|
||||
*/
|
||||
getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange;
|
||||
|
||||
/**
|
||||
* Determines whether expression substitutions are enabled for the provided node.
|
||||
*/
|
||||
isSubstitutionEnabled(node: Node): boolean;
|
||||
|
||||
/**
|
||||
* Determines whether before/after emit notifications should be raised in the pretty
|
||||
* printer when it emits a node.
|
||||
*/
|
||||
isEmitNotificationEnabled(node: Node): boolean;
|
||||
|
||||
/**
|
||||
* Hook used by transformers to substitute expressions just before they
|
||||
* are emitted by the pretty printer.
|
||||
* Emits the substitute for a node, if one is available; otherwise, emits the node.
|
||||
*
|
||||
* @param emitContext The current emit context.
|
||||
* @param node The node to substitute.
|
||||
* @param isExpression A value indicating whether the node is in an expression context.
|
||||
* @param emitCallback A callback used to emit the node or its substitute.
|
||||
*/
|
||||
onSubstituteNode(node: Node, isExpression: boolean): Node;
|
||||
emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void;
|
||||
|
||||
/**
|
||||
* Hook used to allow transformers to capture state before or after
|
||||
* the printer emits a node.
|
||||
* Emits a node with possible notification.
|
||||
*
|
||||
* @param emitContext The current emit context.
|
||||
* @param node The node to emit.
|
||||
* @param emitCallback A callback used to emit the node.
|
||||
*/
|
||||
onEmitNode(node: Node, emitCallback: (node: Node) => void): void;
|
||||
|
||||
/**
|
||||
* Reset transient transformation properties on parse tree nodes.
|
||||
*/
|
||||
dispose(): void;
|
||||
emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void;
|
||||
}
|
||||
|
||||
export interface TransformationContext extends LexicalEnvironment {
|
||||
@@ -76,46 +55,6 @@ namespace ts {
|
||||
getEmitResolver(): EmitResolver;
|
||||
getEmitHost(): EmitHost;
|
||||
|
||||
/**
|
||||
* Gets flags used to customize later transformations or emit.
|
||||
*/
|
||||
getNodeEmitFlags(node: Node): NodeEmitFlags;
|
||||
|
||||
/**
|
||||
* Sets flags used to customize later transformations or emit.
|
||||
*/
|
||||
setNodeEmitFlags<T extends Node>(node: T, flags: NodeEmitFlags): T;
|
||||
|
||||
/**
|
||||
* Gets the TextRange to use for source maps for the node.
|
||||
*/
|
||||
getSourceMapRange(node: Node): TextRange;
|
||||
|
||||
/**
|
||||
* Sets the TextRange to use for source maps for the node.
|
||||
*/
|
||||
setSourceMapRange<T extends Node>(node: T, range: TextRange): T;
|
||||
|
||||
/**
|
||||
* Gets the TextRange to use for source maps for a token of a node.
|
||||
*/
|
||||
getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange;
|
||||
|
||||
/**
|
||||
* Sets the TextRange to use for source maps for a token of a node.
|
||||
*/
|
||||
setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: TextRange): T;
|
||||
|
||||
/**
|
||||
* Gets the TextRange to use for comments for the node.
|
||||
*/
|
||||
getCommentRange(node: Node): TextRange;
|
||||
|
||||
/**
|
||||
* Sets the TextRange to use for comments for the node.
|
||||
*/
|
||||
setCommentRange<T extends Node>(node: T, range: TextRange): T;
|
||||
|
||||
/**
|
||||
* Hoists a function declaration to the containing scope.
|
||||
*/
|
||||
@@ -140,7 +79,7 @@ namespace ts {
|
||||
* Hook used by transformers to substitute expressions just before they
|
||||
* are emitted by the pretty printer.
|
||||
*/
|
||||
onSubstituteNode?: (node: Node, isExpression: boolean) => Node;
|
||||
onSubstituteNode?: (emitContext: EmitContext, node: Node) => Node;
|
||||
|
||||
/**
|
||||
* Enables before/after emit notifications in the pretty printer for the provided
|
||||
@@ -158,7 +97,7 @@ namespace ts {
|
||||
* Hook used to allow transformers to capture state before or after
|
||||
* the printer emits a node.
|
||||
*/
|
||||
onEmitNode?: (node: Node, emit: (node: Node) => void) => void;
|
||||
onEmitNode?: (emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) => void;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -188,14 +127,6 @@ namespace ts {
|
||||
return transformers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a monotonically increasing transformation id used to associate a node with a specific
|
||||
* transformation. This ensures transient properties related to transformations can be safely
|
||||
* stored on source tree nodes that may be reused across multiple transformations (such as
|
||||
* with compile-on-save).
|
||||
*/
|
||||
let nextTransformId = 1;
|
||||
|
||||
/**
|
||||
* Transforms an array of SourceFiles by passing them through each transformer.
|
||||
*
|
||||
@@ -205,18 +136,10 @@ namespace ts {
|
||||
* @param transforms An array of Transformers.
|
||||
*/
|
||||
export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult {
|
||||
const transformId = nextTransformId;
|
||||
nextTransformId++;
|
||||
|
||||
const tokenSourceMapRanges = createMap<TextRange>();
|
||||
const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = [];
|
||||
const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = [];
|
||||
const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count);
|
||||
const parseTreeNodesWithAnnotations: Node[] = [];
|
||||
|
||||
let lastTokenSourceMapRangeNode: Node;
|
||||
let lastTokenSourceMapRangeToken: SyntaxKind;
|
||||
let lastTokenSourceMapRange: TextRange;
|
||||
let lexicalEnvironmentStackOffset = 0;
|
||||
let hoistedVariableDeclarations: VariableDeclaration[];
|
||||
let hoistedFunctionDeclarations: FunctionDeclaration[];
|
||||
@@ -228,22 +151,14 @@ namespace ts {
|
||||
getCompilerOptions: () => host.getCompilerOptions(),
|
||||
getEmitResolver: () => resolver,
|
||||
getEmitHost: () => host,
|
||||
getNodeEmitFlags,
|
||||
setNodeEmitFlags,
|
||||
getSourceMapRange,
|
||||
setSourceMapRange,
|
||||
getTokenSourceMapRange,
|
||||
setTokenSourceMapRange,
|
||||
getCommentRange,
|
||||
setCommentRange,
|
||||
hoistVariableDeclaration,
|
||||
hoistFunctionDeclaration,
|
||||
startLexicalEnvironment,
|
||||
endLexicalEnvironment,
|
||||
onSubstituteNode,
|
||||
onSubstituteNode: (emitContext, node) => node,
|
||||
enableSubstitution,
|
||||
isSubstitutionEnabled,
|
||||
onEmitNode,
|
||||
onEmitNode: (node, emitContext, emitCallback) => emitCallback(node, emitContext),
|
||||
enableEmitNotification,
|
||||
isEmitNotificationEnabled
|
||||
};
|
||||
@@ -258,30 +173,9 @@ namespace ts {
|
||||
lexicalEnvironmentDisabled = true;
|
||||
|
||||
return {
|
||||
getSourceFiles: () => transformed,
|
||||
getTokenSourceMapRange,
|
||||
isSubstitutionEnabled,
|
||||
isEmitNotificationEnabled,
|
||||
onSubstituteNode: context.onSubstituteNode,
|
||||
onEmitNode: context.onEmitNode,
|
||||
dispose() {
|
||||
// During transformation we may need to annotate a parse tree node with transient
|
||||
// transformation properties. As parse tree nodes live longer than transformation
|
||||
// nodes, we need to make sure we reclaim any memory allocated for custom ranges
|
||||
// from these nodes to ensure we do not hold onto entire subtrees just for position
|
||||
// information. We also need to reset these nodes to a pre-transformation state
|
||||
// for incremental parsing scenarios so that we do not impact later emit.
|
||||
for (const node of parseTreeNodesWithAnnotations) {
|
||||
if (node.transformId === transformId) {
|
||||
node.transformId = 0;
|
||||
node.emitFlags = 0;
|
||||
node.commentRange = undefined;
|
||||
node.sourceMapRange = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
parseTreeNodesWithAnnotations.length = 0;
|
||||
}
|
||||
transformed,
|
||||
emitNodeWithSubstitution,
|
||||
emitNodeWithNotification
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -308,18 +202,29 @@ namespace ts {
|
||||
* Determines whether expression substitutions are enabled for the provided node.
|
||||
*/
|
||||
function isSubstitutionEnabled(node: Node) {
|
||||
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0;
|
||||
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0
|
||||
&& (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default hook for node substitutions.
|
||||
* Emits a node with possible substitution.
|
||||
*
|
||||
* @param node The node to substitute.
|
||||
* @param isExpression A value indicating whether the node is to be used in an expression
|
||||
* position.
|
||||
* @param emitContext The current emit context.
|
||||
* @param node The node to emit.
|
||||
* @param emitCallback The callback used to emit the node or its substitute.
|
||||
*/
|
||||
function onSubstituteNode(node: Node, isExpression: boolean) {
|
||||
return node;
|
||||
function emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) {
|
||||
if (node) {
|
||||
if (isSubstitutionEnabled(node)) {
|
||||
const substitute = context.onSubstituteNode(emitContext, node);
|
||||
if (substitute && substitute !== node) {
|
||||
emitCallback(emitContext, substitute);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
emitCallback(emitContext, node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,154 +240,25 @@ namespace ts {
|
||||
*/
|
||||
function isEmitNotificationEnabled(node: Node) {
|
||||
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0
|
||||
|| (getNodeEmitFlags(node) & NodeEmitFlags.AdviseOnEmitNode) !== 0;
|
||||
|| (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default hook for node emit.
|
||||
* Emits a node with possible emit notification.
|
||||
*
|
||||
* @param emitContext The current emit context.
|
||||
* @param node The node to emit.
|
||||
* @param emit A callback used to emit the node in the printer.
|
||||
* @param emitCallback The callback used to emit the node.
|
||||
*/
|
||||
function onEmitNode(node: Node, emit: (node: Node) => void) {
|
||||
emit(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a node with the current transformation, initializing
|
||||
* various transient transformation properties.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
function beforeSetAnnotation(node: Node) {
|
||||
if ((node.flags & NodeFlags.Synthesized) === 0 && node.transformId !== transformId) {
|
||||
// To avoid holding onto transformation artifacts, we keep track of any
|
||||
// parse tree node we are annotating. This allows us to clean them up after
|
||||
// all transformations have completed.
|
||||
parseTreeNodesWithAnnotations.push(node);
|
||||
node.transformId = transformId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets flags that control emit behavior of a node.
|
||||
*
|
||||
* If the node does not have its own NodeEmitFlags set, the node emit flags of its
|
||||
* original pointer are used.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
function getNodeEmitFlags(node: Node) {
|
||||
return node.emitFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets flags that control emit behavior of a node.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param emitFlags The NodeEmitFlags for the node.
|
||||
*/
|
||||
function setNodeEmitFlags<T extends Node>(node: T, emitFlags: NodeEmitFlags) {
|
||||
beforeSetAnnotation(node);
|
||||
node.emitFlags = emitFlags;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom text range to use when emitting source maps.
|
||||
*
|
||||
* If a node does not have its own custom source map text range, the custom source map
|
||||
* text range of its original pointer is used.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
function getSourceMapRange(node: Node) {
|
||||
return node.sourceMapRange || node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom text range to use when emitting source maps.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param range The text range.
|
||||
*/
|
||||
function setSourceMapRange<T extends Node>(node: T, range: TextRange) {
|
||||
beforeSetAnnotation(node);
|
||||
node.sourceMapRange = range;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TextRange to use for source maps for a token of a node.
|
||||
*
|
||||
* If a node does not have its own custom source map text range for a token, the custom
|
||||
* source map text range for the token of its original pointer is used.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param token The token.
|
||||
*/
|
||||
function getTokenSourceMapRange(node: Node, token: SyntaxKind) {
|
||||
// As a performance optimization, use the cached value of the most recent node.
|
||||
// This helps for cases where this function is called repeatedly for the same node.
|
||||
if (lastTokenSourceMapRangeNode === node && lastTokenSourceMapRangeToken === token) {
|
||||
return lastTokenSourceMapRange;
|
||||
}
|
||||
|
||||
// Get the custom token source map range for a node or from one of its original nodes.
|
||||
// Custom token ranges are not stored on the node to avoid the GC burden.
|
||||
let range: TextRange;
|
||||
let current = node;
|
||||
while (current) {
|
||||
range = current.id ? tokenSourceMapRanges[current.id + "-" + token] : undefined;
|
||||
if (range !== undefined) {
|
||||
break;
|
||||
function emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) {
|
||||
if (node) {
|
||||
if (isEmitNotificationEnabled(node)) {
|
||||
context.onEmitNode(emitContext, node, emitCallback);
|
||||
}
|
||||
else {
|
||||
emitCallback(emitContext, node);
|
||||
}
|
||||
|
||||
current = current.original;
|
||||
}
|
||||
|
||||
// Cache the most recently requested value.
|
||||
lastTokenSourceMapRangeNode = node;
|
||||
lastTokenSourceMapRangeToken = token;
|
||||
lastTokenSourceMapRange = range;
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TextRange to use for source maps for a token of a node.
|
||||
*
|
||||
* @param node The node.
|
||||
* @param token The token.
|
||||
* @param range The text range.
|
||||
*/
|
||||
function setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: TextRange) {
|
||||
// Cache the most recently requested value.
|
||||
lastTokenSourceMapRangeNode = node;
|
||||
lastTokenSourceMapRangeToken = token;
|
||||
lastTokenSourceMapRange = range;
|
||||
tokenSourceMapRanges[getNodeId(node) + "-" + token] = range;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a custom text range to use when emitting comments.
|
||||
*
|
||||
* If a node does not have its own custom source map text range, the custom source map
|
||||
* text range of its original pointer is used.
|
||||
*
|
||||
* @param node The node.
|
||||
*/
|
||||
function getCommentRange(node: Node) {
|
||||
return node.commentRange || node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom text range to use when emitting comments.
|
||||
*/
|
||||
function setCommentRange<T extends Node>(node: T, range: TextRange) {
|
||||
beforeSetAnnotation(node);
|
||||
node.commentRange = range;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,70 +341,4 @@ namespace ts {
|
||||
return statements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High-order function, creates a function that executes a function composition.
|
||||
* For example, `chain(a, b)` is the equivalent of `x => ((a', b') => y => b'(a'(y)))(a(x), b(x))`
|
||||
*
|
||||
* @param args The functions to chain.
|
||||
*/
|
||||
function chain<T, U>(...args: ((t: T) => (u: U) => U)[]): (t: T) => (u: U) => U;
|
||||
function chain<T, U>(a: (t: T) => (u: U) => U, b: (t: T) => (u: U) => U, c: (t: T) => (u: U) => U, d: (t: T) => (u: U) => U, e: (t: T) => (u: U) => U): (t: T) => (u: U) => U {
|
||||
if (e) {
|
||||
const args: ((t: T) => (u: U) => U)[] = [];
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
}
|
||||
|
||||
return t => compose(...map(args, f => f(t)));
|
||||
}
|
||||
else if (d) {
|
||||
return t => compose(a(t), b(t), c(t), d(t));
|
||||
}
|
||||
else if (c) {
|
||||
return t => compose(a(t), b(t), c(t));
|
||||
}
|
||||
else if (b) {
|
||||
return t => compose(a(t), b(t));
|
||||
}
|
||||
else if (a) {
|
||||
return t => compose(a(t));
|
||||
}
|
||||
else {
|
||||
return t => u => u;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High-order function, composes functions. Note that functions are composed inside-out;
|
||||
* for example, `compose(a, b)` is the equivalent of `x => b(a(x))`.
|
||||
*
|
||||
* @param args The functions to compose.
|
||||
*/
|
||||
function compose<T>(...args: ((t: T) => T)[]): (t: T) => T;
|
||||
function compose<T>(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T {
|
||||
if (e) {
|
||||
const args: ((t: T) => T)[] = [];
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
}
|
||||
|
||||
return t => reduceLeft<(t: T) => T, T>(args, (u, f) => f(u), t);
|
||||
}
|
||||
else if (d) {
|
||||
return t => d(c(b(a(t))));
|
||||
}
|
||||
else if (c) {
|
||||
return t => c(b(a(t)));
|
||||
}
|
||||
else if (b) {
|
||||
return t => b(a(t));
|
||||
}
|
||||
else if (a) {
|
||||
return t => a(t);
|
||||
}
|
||||
else {
|
||||
return t => t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace ts {
|
||||
|
||||
// NOTE: this completely disables source maps, but aligns with the behavior of
|
||||
// `emitAssignment` in the old emitter.
|
||||
context.setNodeEmitFlags(expression, NodeEmitFlags.NoNestedSourceMaps);
|
||||
setEmitFlags(expression, EmitFlags.NoNestedSourceMaps);
|
||||
|
||||
aggregateTransformFlags(expression);
|
||||
expressions.push(expression);
|
||||
@@ -102,7 +102,7 @@ namespace ts {
|
||||
|
||||
// NOTE: this completely disables source maps, but aligns with the behavior of
|
||||
// `emitAssignment` in the old emitter.
|
||||
context.setNodeEmitFlags(declaration, NodeEmitFlags.NoNestedSourceMaps);
|
||||
setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps);
|
||||
|
||||
aggregateTransformFlags(declaration);
|
||||
declarations.push(declaration);
|
||||
@@ -147,7 +147,7 @@ namespace ts {
|
||||
|
||||
// NOTE: this completely disables source maps, but aligns with the behavior of
|
||||
// `emitAssignment` in the old emitter.
|
||||
context.setNodeEmitFlags(declaration, NodeEmitFlags.NoNestedSourceMaps);
|
||||
setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps);
|
||||
|
||||
declarations.push(declaration);
|
||||
aggregateTransformFlags(declaration);
|
||||
@@ -211,7 +211,7 @@ namespace ts {
|
||||
|
||||
// NOTE: this completely disables source maps, but aligns with the behavior of
|
||||
// `emitAssignment` in the old emitter.
|
||||
context.setNodeEmitFlags(expression, NodeEmitFlags.NoNestedSourceMaps);
|
||||
setEmitFlags(expression, EmitFlags.NoNestedSourceMaps);
|
||||
|
||||
pendingAssignments.push(expression);
|
||||
return expression;
|
||||
@@ -271,8 +271,8 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
const name = getMutableClone(<Identifier>target);
|
||||
context.setSourceMapRange(name, target);
|
||||
context.setCommentRange(name, target);
|
||||
setSourceMapRange(name, target);
|
||||
setCommentRange(name, target);
|
||||
emitAssignment(name, value, location, /*original*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
+295
-118
@@ -139,18 +139,35 @@ namespace ts {
|
||||
loopOutParameters?: LoopOutParameter[];
|
||||
}
|
||||
|
||||
const enum SuperCaptureResult {
|
||||
/**
|
||||
* A capture may have been added for calls to 'super', but
|
||||
* the caller should emit subsequent statements normally.
|
||||
*/
|
||||
NoReplacement,
|
||||
/**
|
||||
* A call to 'super()' got replaced with a capturing statement like:
|
||||
*
|
||||
* var _this = _super.call(...) || this;
|
||||
*
|
||||
* Callers should skip the current statement.
|
||||
*/
|
||||
ReplaceSuperCapture,
|
||||
/**
|
||||
* A call to 'super()' got replaced with a capturing statement like:
|
||||
*
|
||||
* return _super.call(...) || this;
|
||||
*
|
||||
* Callers should skip the current statement and avoid any returns of '_this'.
|
||||
*/
|
||||
ReplaceWithReturn,
|
||||
}
|
||||
|
||||
export function transformES6(context: TransformationContext) {
|
||||
const {
|
||||
startLexicalEnvironment,
|
||||
endLexicalEnvironment,
|
||||
hoistVariableDeclaration,
|
||||
getNodeEmitFlags,
|
||||
setNodeEmitFlags,
|
||||
getCommentRange,
|
||||
setCommentRange,
|
||||
getSourceMapRange,
|
||||
setSourceMapRange,
|
||||
setTokenSourceMapRange,
|
||||
} = context;
|
||||
|
||||
const resolver = context.getEmitResolver();
|
||||
@@ -185,6 +202,10 @@ namespace ts {
|
||||
return transformSourceFile;
|
||||
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
currentSourceFile = node;
|
||||
currentText = node.text;
|
||||
return visitNode(node, visitor, isSourceFile);
|
||||
@@ -200,7 +221,7 @@ namespace ts {
|
||||
: visitorWorker(node);
|
||||
}
|
||||
|
||||
function saveStateAndInvoke<T>(node: Node, f: (node: Node) => T): T {
|
||||
function saveStateAndInvoke<T extends Node, U>(node: T, f: (node: T) => U): U {
|
||||
const savedEnclosingFunction = enclosingFunction;
|
||||
const savedEnclosingNonArrowFunction = enclosingNonArrowFunction;
|
||||
const savedEnclosingNonAsyncFunctionBody = enclosingNonAsyncFunctionBody;
|
||||
@@ -408,7 +429,7 @@ namespace ts {
|
||||
enclosingFunction = currentNode;
|
||||
if (currentNode.kind !== SyntaxKind.ArrowFunction) {
|
||||
enclosingNonArrowFunction = currentNode;
|
||||
if (!(currentNode.emitFlags & NodeEmitFlags.AsyncFunctionBody)) {
|
||||
if (!(getEmitFlags(currentNode) & EmitFlags.AsyncFunctionBody)) {
|
||||
enclosingNonAsyncFunctionBody = currentNode;
|
||||
}
|
||||
}
|
||||
@@ -671,19 +692,19 @@ namespace ts {
|
||||
// To preserve the behavior of the old emitter, we explicitly indent
|
||||
// the body of the function here if it was requested in an earlier
|
||||
// transformation.
|
||||
if (getNodeEmitFlags(node) & NodeEmitFlags.Indented) {
|
||||
setNodeEmitFlags(classFunction, NodeEmitFlags.Indented);
|
||||
if (getEmitFlags(node) & EmitFlags.Indented) {
|
||||
setEmitFlags(classFunction, EmitFlags.Indented);
|
||||
}
|
||||
|
||||
// "inner" and "outer" below are added purely to preserve source map locations from
|
||||
// the old emitter
|
||||
const inner = createPartiallyEmittedExpression(classFunction);
|
||||
inner.end = node.end;
|
||||
setNodeEmitFlags(inner, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(inner, EmitFlags.NoComments);
|
||||
|
||||
const outer = createPartiallyEmittedExpression(inner);
|
||||
outer.end = skipTrivia(currentText, node.pos);
|
||||
setNodeEmitFlags(outer, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(outer, EmitFlags.NoComments);
|
||||
|
||||
return createParen(
|
||||
createCall(
|
||||
@@ -717,17 +738,17 @@ namespace ts {
|
||||
// emit with the original emitter.
|
||||
const outer = createPartiallyEmittedExpression(localName);
|
||||
outer.end = closingBraceLocation.end;
|
||||
setNodeEmitFlags(outer, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(outer, EmitFlags.NoComments);
|
||||
|
||||
const statement = createReturn(outer);
|
||||
statement.pos = closingBraceLocation.pos;
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoComments | NodeEmitFlags.NoTokenSourceMaps);
|
||||
setEmitFlags(statement, EmitFlags.NoComments | EmitFlags.NoTokenSourceMaps);
|
||||
statements.push(statement);
|
||||
|
||||
addRange(statements, endLexicalEnvironment());
|
||||
|
||||
const block = createBlock(createNodeArray(statements, /*location*/ node.members), /*location*/ undefined, /*multiLine*/ true);
|
||||
setNodeEmitFlags(block, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(block, EmitFlags.NoComments);
|
||||
return block;
|
||||
}
|
||||
|
||||
@@ -759,7 +780,8 @@ namespace ts {
|
||||
function addConstructor(statements: Statement[], node: ClassExpression | ClassDeclaration, extendsClauseElement: ExpressionWithTypeArguments): void {
|
||||
const constructor = getFirstConstructorWithBody(node);
|
||||
const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, extendsClauseElement !== undefined);
|
||||
statements.push(
|
||||
|
||||
const constructorFunction =
|
||||
createFunctionDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
@@ -770,8 +792,12 @@ namespace ts {
|
||||
/*type*/ undefined,
|
||||
transformConstructorBody(constructor, node, extendsClauseElement, hasSynthesizedSuper),
|
||||
/*location*/ constructor || node
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
if (extendsClauseElement) {
|
||||
setEmitFlags(constructorFunction, EmitFlags.CapturesThis);
|
||||
}
|
||||
statements.push(constructorFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,22 +829,53 @@ namespace ts {
|
||||
* @param hasSynthesizedSuper A value indicating whether the constructor starts with a
|
||||
* synthesized `super` call.
|
||||
*/
|
||||
function transformConstructorBody(constructor: ConstructorDeclaration, node: ClassDeclaration | ClassExpression, extendsClauseElement: ExpressionWithTypeArguments, hasSynthesizedSuper: boolean) {
|
||||
function transformConstructorBody(constructor: ConstructorDeclaration | undefined, node: ClassDeclaration | ClassExpression, extendsClauseElement: ExpressionWithTypeArguments, hasSynthesizedSuper: boolean) {
|
||||
const statements: Statement[] = [];
|
||||
startLexicalEnvironment();
|
||||
if (constructor) {
|
||||
addCaptureThisForNodeIfNeeded(statements, constructor);
|
||||
addDefaultValueAssignmentsIfNeeded(statements, constructor);
|
||||
addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper);
|
||||
|
||||
let statementOffset = -1;
|
||||
if (hasSynthesizedSuper) {
|
||||
// If a super call has already been synthesized,
|
||||
// we're going to assume that we should just transform everything after that.
|
||||
// The assumption is that no prior step in the pipeline has added any prologue directives.
|
||||
statementOffset = 1;
|
||||
}
|
||||
else if (constructor) {
|
||||
// Otherwise, try to emit all potential prologue directives first.
|
||||
statementOffset = addPrologueDirectives(statements, constructor.body.statements, /*ensureUseStrict*/ false, visitor);
|
||||
}
|
||||
|
||||
addDefaultSuperCallIfNeeded(statements, constructor, extendsClauseElement, hasSynthesizedSuper);
|
||||
if (constructor) {
|
||||
addDefaultValueAssignmentsIfNeeded(statements, constructor);
|
||||
addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper);
|
||||
Debug.assert(statementOffset >= 0, "statementOffset not initialized correctly!");
|
||||
|
||||
}
|
||||
|
||||
const superCaptureStatus = declareOrCaptureOrReturnThisForConstructorIfNeeded(statements, constructor, !!extendsClauseElement, hasSynthesizedSuper, statementOffset);
|
||||
|
||||
// The last statement expression was replaced. Skip it.
|
||||
if (superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture || superCaptureStatus === SuperCaptureResult.ReplaceWithReturn) {
|
||||
statementOffset++;
|
||||
}
|
||||
|
||||
if (constructor) {
|
||||
const body = saveStateAndInvoke(constructor, hasSynthesizedSuper ? transformConstructorBodyWithSynthesizedSuper : transformConstructorBodyWithoutSynthesizedSuper);
|
||||
const body = saveStateAndInvoke(constructor, constructor => visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset));
|
||||
addRange(statements, body);
|
||||
}
|
||||
|
||||
// Return `_this` unless we're sure enough that it would be pointless to add a return statement.
|
||||
// If there's a constructor that we can tell returns in enough places, then we *do not* want to add a return.
|
||||
if (extendsClauseElement
|
||||
&& superCaptureStatus !== SuperCaptureResult.ReplaceWithReturn
|
||||
&& !(constructor && isSufficientlyCoveredByReturnStatements(constructor.body))) {
|
||||
statements.push(
|
||||
createReturn(
|
||||
createIdentifier("_this")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addRange(statements, endLexicalEnvironment());
|
||||
const block = createBlock(
|
||||
createNodeArray(
|
||||
@@ -830,47 +887,136 @@ namespace ts {
|
||||
);
|
||||
|
||||
if (!constructor) {
|
||||
setNodeEmitFlags(block, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(block, EmitFlags.NoComments);
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
function transformConstructorBodyWithSynthesizedSuper(node: ConstructorDeclaration) {
|
||||
return visitNodes(node.body.statements, visitor, isStatement, 1);
|
||||
}
|
||||
/**
|
||||
* We want to try to avoid emitting a return statement in certain cases if a user already returned something.
|
||||
* It would generate obviously dead code, so we'll try to make things a little bit prettier
|
||||
* by doing a minimal check on whether some common patterns always explicitly return.
|
||||
*/
|
||||
function isSufficientlyCoveredByReturnStatements(statement: Statement): boolean {
|
||||
// A return statement is considered covered.
|
||||
if (statement.kind === SyntaxKind.ReturnStatement) {
|
||||
return true;
|
||||
}
|
||||
// An if-statement with two covered branches is covered.
|
||||
else if (statement.kind === SyntaxKind.IfStatement) {
|
||||
const ifStatement = statement as IfStatement;
|
||||
if (ifStatement.elseStatement) {
|
||||
return isSufficientlyCoveredByReturnStatements(ifStatement.thenStatement) &&
|
||||
isSufficientlyCoveredByReturnStatements(ifStatement.elseStatement);
|
||||
}
|
||||
}
|
||||
// A block is covered if it has a last statement which is covered.
|
||||
else if (statement.kind === SyntaxKind.Block) {
|
||||
const lastStatement = lastOrUndefined((statement as Block).statements);
|
||||
if (lastStatement && isSufficientlyCoveredByReturnStatements(lastStatement)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function transformConstructorBodyWithoutSynthesizedSuper(node: ConstructorDeclaration) {
|
||||
return visitNodes(node.body.statements, visitor, isStatement, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a synthesized call to `_super` if it is needed.
|
||||
* Declares a `_this` variable for derived classes and for when arrow functions capture `this`.
|
||||
*
|
||||
* @param statements The statements for the new constructor body.
|
||||
* @param constructor The constructor for the class.
|
||||
* @param extendsClauseElement The expression for the class `extends` clause.
|
||||
* @param hasSynthesizedSuper A value indicating whether the constructor starts with a
|
||||
* synthesized `super` call.
|
||||
* @returns The new statement offset into the `statements` array.
|
||||
*/
|
||||
function addDefaultSuperCallIfNeeded(statements: Statement[], constructor: ConstructorDeclaration, extendsClauseElement: ExpressionWithTypeArguments, hasSynthesizedSuper: boolean) {
|
||||
// If the TypeScript transformer needed to synthesize a constructor for property
|
||||
// initializers, it would have also added a synthetic `...args` parameter and
|
||||
// `super` call.
|
||||
// If this is the case, or if the class has an `extends` clause but no
|
||||
// constructor, we emit a synthesized call to `_super`.
|
||||
if (constructor ? hasSynthesizedSuper : extendsClauseElement) {
|
||||
statements.push(
|
||||
createStatement(
|
||||
createFunctionApply(
|
||||
createIdentifier("_super"),
|
||||
createThis(),
|
||||
createIdentifier("arguments")
|
||||
),
|
||||
/*location*/ extendsClauseElement
|
||||
)
|
||||
);
|
||||
function declareOrCaptureOrReturnThisForConstructorIfNeeded(
|
||||
statements: Statement[],
|
||||
ctor: ConstructorDeclaration | undefined,
|
||||
hasExtendsClause: boolean,
|
||||
hasSynthesizedSuper: boolean,
|
||||
statementOffset: number) {
|
||||
// If this isn't a derived class, just capture 'this' for arrow functions if necessary.
|
||||
if (!hasExtendsClause) {
|
||||
if (ctor) {
|
||||
addCaptureThisForNodeIfNeeded(statements, ctor);
|
||||
}
|
||||
return SuperCaptureResult.NoReplacement;
|
||||
}
|
||||
|
||||
// We must be here because the user didn't write a constructor
|
||||
// but we needed to call 'super(...args)' anyway as per 14.5.14 of the ES2016 spec.
|
||||
// If that's the case we can just immediately return the result of a 'super()' call.
|
||||
if (!ctor) {
|
||||
statements.push(createReturn(createDefaultSuperCallOrThis()));
|
||||
return SuperCaptureResult.ReplaceWithReturn;
|
||||
}
|
||||
|
||||
// The constructor exists, but it and the 'super()' call it contains were generated
|
||||
// for something like property initializers.
|
||||
// Create a captured '_this' variable and assume it will subsequently be used.
|
||||
if (hasSynthesizedSuper) {
|
||||
captureThisForNode(statements, ctor, createDefaultSuperCallOrThis());
|
||||
enableSubstitutionsForCapturedThis();
|
||||
return SuperCaptureResult.ReplaceSuperCapture;
|
||||
}
|
||||
|
||||
// Most of the time, a 'super' call will be the first real statement in a constructor body.
|
||||
// In these cases, we'd like to transform these into a *single* statement instead of a declaration
|
||||
// followed by an assignment statement for '_this'. For instance, if we emitted without an initializer,
|
||||
// we'd get:
|
||||
//
|
||||
// var _this;
|
||||
// _this = _super.call(...) || this;
|
||||
//
|
||||
// instead of
|
||||
//
|
||||
// var _this = _super.call(...) || this;
|
||||
//
|
||||
// Additionally, if the 'super()' call is the last statement, we should just avoid capturing
|
||||
// entirely and immediately return the result like so:
|
||||
//
|
||||
// return _super.call(...) || this;
|
||||
//
|
||||
let firstStatement: Statement;
|
||||
let superCallExpression: Expression;
|
||||
|
||||
const ctorStatements = ctor.body.statements;
|
||||
if (statementOffset < ctorStatements.length) {
|
||||
firstStatement = ctorStatements[statementOffset];
|
||||
|
||||
if (firstStatement.kind === SyntaxKind.ExpressionStatement && isSuperCallExpression((firstStatement as ExpressionStatement).expression)) {
|
||||
const superCall = (firstStatement as ExpressionStatement).expression as CallExpression;
|
||||
superCallExpression = setOriginalNode(
|
||||
saveStateAndInvoke(superCall, visitImmediateSuperCallInBody),
|
||||
superCall
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the result if we have an immediate super() call on the last statement.
|
||||
if (superCallExpression && statementOffset === ctorStatements.length - 1) {
|
||||
statements.push(createReturn(superCallExpression));
|
||||
return SuperCaptureResult.ReplaceWithReturn;
|
||||
}
|
||||
|
||||
// Perform the capture.
|
||||
captureThisForNode(statements, ctor, superCallExpression, firstStatement);
|
||||
|
||||
// If we're actually replacing the original statement, we need to signal this to the caller.
|
||||
if (superCallExpression) {
|
||||
return SuperCaptureResult.ReplaceSuperCapture;
|
||||
}
|
||||
|
||||
return SuperCaptureResult.NoReplacement;
|
||||
}
|
||||
|
||||
function createDefaultSuperCallOrThis() {
|
||||
const actualThis = createThis();
|
||||
setEmitFlags(actualThis, EmitFlags.NoSubstitution);
|
||||
const superCall = createFunctionApply(
|
||||
createIdentifier("_super"),
|
||||
actualThis,
|
||||
createIdentifier("arguments"),
|
||||
);
|
||||
return createLogicalOr(superCall, actualThis);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -967,27 +1113,27 @@ namespace ts {
|
||||
// of an initializer, we must emit that expression to preserve side effects.
|
||||
if (name.elements.length > 0) {
|
||||
statements.push(
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList(
|
||||
flattenParameterDestructuring(context, parameter, temp, visitor)
|
||||
)
|
||||
),
|
||||
NodeEmitFlags.CustomPrologue
|
||||
EmitFlags.CustomPrologue
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (initializer) {
|
||||
statements.push(
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createStatement(
|
||||
createAssignment(
|
||||
temp,
|
||||
visitNode(initializer, visitor, isExpression)
|
||||
)
|
||||
),
|
||||
NodeEmitFlags.CustomPrologue
|
||||
EmitFlags.CustomPrologue
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1008,23 +1154,23 @@ namespace ts {
|
||||
getSynthesizedClone(name),
|
||||
createVoidZero()
|
||||
),
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createBlock([
|
||||
createStatement(
|
||||
createAssignment(
|
||||
setNodeEmitFlags(getMutableClone(name), NodeEmitFlags.NoSourceMap),
|
||||
setNodeEmitFlags(initializer, NodeEmitFlags.NoSourceMap | getNodeEmitFlags(initializer)),
|
||||
setEmitFlags(getMutableClone(name), EmitFlags.NoSourceMap),
|
||||
setEmitFlags(initializer, EmitFlags.NoSourceMap | getEmitFlags(initializer)),
|
||||
/*location*/ parameter
|
||||
)
|
||||
)
|
||||
], /*location*/ parameter),
|
||||
NodeEmitFlags.SingleLine | NodeEmitFlags.NoTrailingSourceMap | NodeEmitFlags.NoTokenSourceMaps
|
||||
EmitFlags.SingleLine | EmitFlags.NoTrailingSourceMap | EmitFlags.NoTokenSourceMaps
|
||||
),
|
||||
/*elseStatement*/ undefined,
|
||||
/*location*/ parameter
|
||||
);
|
||||
statement.startsOnNewLine = true;
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoTokenSourceMaps | NodeEmitFlags.NoTrailingSourceMap | NodeEmitFlags.CustomPrologue);
|
||||
setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.CustomPrologue);
|
||||
statements.push(statement);
|
||||
}
|
||||
|
||||
@@ -1057,7 +1203,7 @@ namespace ts {
|
||||
|
||||
// `declarationName` is the name of the local declaration for the parameter.
|
||||
const declarationName = getMutableClone(<Identifier>parameter.name);
|
||||
setNodeEmitFlags(declarationName, NodeEmitFlags.NoSourceMap);
|
||||
setEmitFlags(declarationName, EmitFlags.NoSourceMap);
|
||||
|
||||
// `expressionName` is the name of the parameter used in expressions.
|
||||
const expressionName = getSynthesizedClone(<Identifier>parameter.name);
|
||||
@@ -1066,7 +1212,7 @@ namespace ts {
|
||||
|
||||
// var param = [];
|
||||
statements.push(
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList([
|
||||
@@ -1078,7 +1224,7 @@ namespace ts {
|
||||
]),
|
||||
/*location*/ parameter
|
||||
),
|
||||
NodeEmitFlags.CustomPrologue
|
||||
EmitFlags.CustomPrologue
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1111,7 +1257,7 @@ namespace ts {
|
||||
])
|
||||
);
|
||||
|
||||
setNodeEmitFlags(forStatement, NodeEmitFlags.CustomPrologue);
|
||||
setEmitFlags(forStatement, EmitFlags.CustomPrologue);
|
||||
startOnNewLine(forStatement);
|
||||
statements.push(forStatement);
|
||||
}
|
||||
@@ -1124,24 +1270,29 @@ namespace ts {
|
||||
*/
|
||||
function addCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): void {
|
||||
if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) {
|
||||
enableSubstitutionsForCapturedThis();
|
||||
const captureThisStatement = createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList([
|
||||
createVariableDeclaration(
|
||||
"_this",
|
||||
/*type*/ undefined,
|
||||
createThis()
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
setNodeEmitFlags(captureThisStatement, NodeEmitFlags.NoComments | NodeEmitFlags.CustomPrologue);
|
||||
setSourceMapRange(captureThisStatement, node);
|
||||
statements.push(captureThisStatement);
|
||||
captureThisForNode(statements, node, createThis());
|
||||
}
|
||||
}
|
||||
|
||||
function captureThisForNode(statements: Statement[], node: Node, initializer: Expression | undefined, originalStatement?: Statement): void {
|
||||
enableSubstitutionsForCapturedThis();
|
||||
const captureThisStatement = createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList([
|
||||
createVariableDeclaration(
|
||||
"_this",
|
||||
/*type*/ undefined,
|
||||
initializer
|
||||
)
|
||||
]),
|
||||
originalStatement
|
||||
);
|
||||
|
||||
setEmitFlags(captureThisStatement, EmitFlags.NoComments | EmitFlags.CustomPrologue);
|
||||
setSourceMapRange(captureThisStatement, node);
|
||||
statements.push(captureThisStatement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds statements to the class body function for a class to define the members of the
|
||||
* class.
|
||||
@@ -1200,7 +1351,7 @@ namespace ts {
|
||||
const sourceMapRange = getSourceMapRange(member);
|
||||
|
||||
const func = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined);
|
||||
setNodeEmitFlags(func, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(func, EmitFlags.NoComments);
|
||||
setSourceMapRange(func, sourceMapRange);
|
||||
|
||||
const statement = createStatement(
|
||||
@@ -1221,7 +1372,7 @@ namespace ts {
|
||||
// The location for the statement is used to emit comments only.
|
||||
// No source map should be emitted for this statement to align with the
|
||||
// old emitter.
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoSourceMap);
|
||||
setEmitFlags(statement, EmitFlags.NoSourceMap);
|
||||
return statement;
|
||||
}
|
||||
|
||||
@@ -1240,7 +1391,7 @@ namespace ts {
|
||||
// The location for the statement is used to emit source maps only.
|
||||
// No comments should be emitted for this statement to align with the
|
||||
// old emitter.
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(statement, EmitFlags.NoComments);
|
||||
return statement;
|
||||
}
|
||||
|
||||
@@ -1254,11 +1405,11 @@ namespace ts {
|
||||
// To align with source maps in the old emitter, the receiver and property name
|
||||
// arguments are both mapped contiguously to the accessor name.
|
||||
const target = getMutableClone(receiver);
|
||||
setNodeEmitFlags(target, NodeEmitFlags.NoComments | NodeEmitFlags.NoTrailingSourceMap);
|
||||
setEmitFlags(target, EmitFlags.NoComments | EmitFlags.NoTrailingSourceMap);
|
||||
setSourceMapRange(target, firstAccessor.name);
|
||||
|
||||
const propertyName = createExpressionForPropertyName(visitNode(firstAccessor.name, visitor, isPropertyName));
|
||||
setNodeEmitFlags(propertyName, NodeEmitFlags.NoComments | NodeEmitFlags.NoLeadingSourceMap);
|
||||
setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoLeadingSourceMap);
|
||||
setSourceMapRange(propertyName, firstAccessor.name);
|
||||
|
||||
const properties: ObjectLiteralElementLike[] = [];
|
||||
@@ -1309,7 +1460,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const func = transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined);
|
||||
setNodeEmitFlags(func, NodeEmitFlags.CapturesThis);
|
||||
setEmitFlags(func, EmitFlags.CapturesThis);
|
||||
return func;
|
||||
}
|
||||
|
||||
@@ -1434,7 +1585,7 @@ namespace ts {
|
||||
|
||||
const expression = visitNode(body, visitor, isExpression);
|
||||
const returnStatement = createReturn(expression, /*location*/ body);
|
||||
setNodeEmitFlags(returnStatement, NodeEmitFlags.NoTokenSourceMaps | NodeEmitFlags.NoTrailingSourceMap | NodeEmitFlags.NoTrailingComments);
|
||||
setEmitFlags(returnStatement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.NoTrailingComments);
|
||||
statements.push(returnStatement);
|
||||
|
||||
// To align with the source map emit for the old emitter, we set a custom
|
||||
@@ -1452,7 +1603,7 @@ namespace ts {
|
||||
|
||||
const block = createBlock(createNodeArray(statements, statementsLocation), node.body, multiLine);
|
||||
if (!multiLine && singleLine) {
|
||||
setNodeEmitFlags(block, NodeEmitFlags.SingleLine);
|
||||
setEmitFlags(block, EmitFlags.SingleLine);
|
||||
}
|
||||
|
||||
if (closeBraceLocation) {
|
||||
@@ -1538,7 +1689,7 @@ namespace ts {
|
||||
assignment = flattenVariableDestructuringToExpression(context, decl, hoistVariableDeclaration, /*nameSubstitution*/ undefined, visitor);
|
||||
}
|
||||
else {
|
||||
assignment = createBinary(<Identifier>decl.name, SyntaxKind.EqualsToken, decl.initializer);
|
||||
assignment = createBinary(<Identifier>decl.name, SyntaxKind.EqualsToken, visitNode(decl.initializer, visitor, isExpression));
|
||||
}
|
||||
(assignments || (assignments = [])).push(assignment);
|
||||
}
|
||||
@@ -1875,7 +2026,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// The old emitter does not emit source maps for the expression
|
||||
setNodeEmitFlags(expression, NodeEmitFlags.NoSourceMap | getNodeEmitFlags(expression));
|
||||
setEmitFlags(expression, EmitFlags.NoSourceMap | getEmitFlags(expression));
|
||||
|
||||
// The old emitter does not emit source maps for the block.
|
||||
// We add the location to preserve comments.
|
||||
@@ -1884,7 +2035,7 @@ namespace ts {
|
||||
/*location*/ bodyLocation
|
||||
);
|
||||
|
||||
setNodeEmitFlags(body, NodeEmitFlags.NoSourceMap | NodeEmitFlags.NoTokenSourceMaps);
|
||||
setEmitFlags(body, EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps);
|
||||
|
||||
const forStatement = createFor(
|
||||
createVariableDeclarationList([
|
||||
@@ -1902,7 +2053,7 @@ namespace ts {
|
||||
);
|
||||
|
||||
// Disable trailing source maps for the OpenParenToken to align source map emit with the old emitter.
|
||||
setNodeEmitFlags(forStatement, NodeEmitFlags.NoTokenTrailingSourceMaps);
|
||||
setEmitFlags(forStatement, EmitFlags.NoTokenTrailingSourceMaps);
|
||||
return forStatement;
|
||||
}
|
||||
|
||||
@@ -1938,13 +2089,13 @@ namespace ts {
|
||||
const expressions: Expression[] = [];
|
||||
const assignment = createAssignment(
|
||||
temp,
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createObjectLiteral(
|
||||
visitNodes(properties, visitor, isObjectLiteralElementLike, 0, numInitialProperties),
|
||||
/*location*/ undefined,
|
||||
node.multiLine
|
||||
),
|
||||
NodeEmitFlags.Indented
|
||||
EmitFlags.Indented
|
||||
)
|
||||
);
|
||||
if (node.multiLine) {
|
||||
@@ -2067,16 +2218,16 @@ namespace ts {
|
||||
|
||||
const isAsyncBlockContainingAwait =
|
||||
enclosingNonArrowFunction
|
||||
&& (enclosingNonArrowFunction.emitFlags & NodeEmitFlags.AsyncFunctionBody) !== 0
|
||||
&& (getEmitFlags(enclosingNonArrowFunction) & EmitFlags.AsyncFunctionBody) !== 0
|
||||
&& (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0;
|
||||
|
||||
let loopBodyFlags: NodeEmitFlags = 0;
|
||||
let loopBodyFlags: EmitFlags = 0;
|
||||
if (currentState.containsLexicalThis) {
|
||||
loopBodyFlags |= NodeEmitFlags.CapturesThis;
|
||||
loopBodyFlags |= EmitFlags.CapturesThis;
|
||||
}
|
||||
|
||||
if (isAsyncBlockContainingAwait) {
|
||||
loopBodyFlags |= NodeEmitFlags.AsyncFunctionBody;
|
||||
loopBodyFlags |= EmitFlags.AsyncFunctionBody;
|
||||
}
|
||||
|
||||
const convertedLoopVariable =
|
||||
@@ -2087,7 +2238,7 @@ namespace ts {
|
||||
createVariableDeclaration(
|
||||
functionName,
|
||||
/*type*/ undefined,
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createFunctionExpression(
|
||||
isAsyncBlockContainingAwait ? createToken(SyntaxKind.AsteriskToken) : undefined,
|
||||
/*name*/ undefined,
|
||||
@@ -2480,7 +2631,7 @@ namespace ts {
|
||||
// Methods with computed property names are handled in visitObjectLiteralExpression.
|
||||
Debug.assert(!isComputedPropertyName(node.name));
|
||||
const functionExpression = transformFunctionLikeToExpression(node, /*location*/ moveRangePos(node, -1), /*name*/ undefined);
|
||||
setNodeEmitFlags(functionExpression, NodeEmitFlags.NoLeadingComments | getNodeEmitFlags(functionExpression));
|
||||
setEmitFlags(functionExpression, EmitFlags.NoLeadingComments | getEmitFlags(functionExpression));
|
||||
return createPropertyAssignment(
|
||||
node.name,
|
||||
functionExpression,
|
||||
@@ -2526,11 +2677,23 @@ namespace ts {
|
||||
*
|
||||
* @param node a CallExpression.
|
||||
*/
|
||||
function visitCallExpression(node: CallExpression): LeftHandSideExpression {
|
||||
function visitCallExpression(node: CallExpression) {
|
||||
return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true);
|
||||
}
|
||||
|
||||
function visitImmediateSuperCallInBody(node: CallExpression) {
|
||||
return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ false);
|
||||
}
|
||||
|
||||
function visitCallExpressionWithPotentialCapturedThisAssignment(node: CallExpression, assignToCapturedThis: boolean): CallExpression | BinaryExpression {
|
||||
// We are here either because SuperKeyword was used somewhere in the expression, or
|
||||
// because we contain a SpreadElementExpression.
|
||||
|
||||
const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration);
|
||||
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
setEmitFlags(thisArg, EmitFlags.NoSubstitution);
|
||||
}
|
||||
let resultingCall: CallExpression | BinaryExpression;
|
||||
if (node.transformFlags & TransformFlags.ContainsSpreadExpression) {
|
||||
// [source]
|
||||
// f(...a, b)
|
||||
@@ -2546,7 +2709,7 @@ namespace ts {
|
||||
// _super.m.apply(this, a.concat([b]))
|
||||
// _super.prototype.m.apply(this, a.concat([b]))
|
||||
|
||||
return createFunctionApply(
|
||||
resultingCall = createFunctionApply(
|
||||
visitNode(target, visitor, isExpression),
|
||||
visitNode(thisArg, visitor, isExpression),
|
||||
transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false)
|
||||
@@ -2563,13 +2726,27 @@ namespace ts {
|
||||
// _super.m.call(this, a)
|
||||
// _super.prototype.m.call(this, a)
|
||||
|
||||
return createFunctionCall(
|
||||
resultingCall = createFunctionCall(
|
||||
visitNode(target, visitor, isExpression),
|
||||
visitNode(thisArg, visitor, isExpression),
|
||||
visitNodes(node.arguments, visitor, isExpression),
|
||||
/*location*/ node
|
||||
);
|
||||
}
|
||||
|
||||
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
const actualThis = createThis();
|
||||
setEmitFlags(actualThis, EmitFlags.NoSubstitution);
|
||||
const initializer =
|
||||
createLogicalOr(
|
||||
resultingCall,
|
||||
actualThis
|
||||
);
|
||||
return assignToCapturedThis
|
||||
? createAssignment(createIdentifier("_this"), initializer)
|
||||
: initializer;
|
||||
}
|
||||
return resultingCall;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2855,7 +3032,7 @@ namespace ts {
|
||||
*
|
||||
* @param node The node to be printed.
|
||||
*/
|
||||
function onEmitNode(node: Node, emit: (node: Node) => void) {
|
||||
function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) {
|
||||
const savedEnclosingFunction = enclosingFunction;
|
||||
|
||||
if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && isFunctionLike(node)) {
|
||||
@@ -2863,7 +3040,7 @@ namespace ts {
|
||||
enclosingFunction = node;
|
||||
}
|
||||
|
||||
previousOnEmitNode(node, emit);
|
||||
previousOnEmitNode(emitContext, node, emitCallback);
|
||||
|
||||
enclosingFunction = savedEnclosingFunction;
|
||||
}
|
||||
@@ -2904,10 +3081,10 @@ namespace ts {
|
||||
* @param isExpression A value indicating whether the node is to be used in an expression
|
||||
* position.
|
||||
*/
|
||||
function onSubstituteNode(node: Node, isExpression: boolean) {
|
||||
node = previousOnSubstituteNode(node, isExpression);
|
||||
function onSubstituteNode(emitContext: EmitContext, node: Node) {
|
||||
node = previousOnSubstituteNode(emitContext, node);
|
||||
|
||||
if (isExpression) {
|
||||
if (emitContext === EmitContext.Expression) {
|
||||
return substituteExpression(node);
|
||||
}
|
||||
|
||||
@@ -2995,7 +3172,7 @@ namespace ts {
|
||||
function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression {
|
||||
if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis
|
||||
&& enclosingFunction
|
||||
&& enclosingFunction.emitFlags & NodeEmitFlags.CapturesThis) {
|
||||
&& getEmitFlags(enclosingFunction) & EmitFlags.CapturesThis) {
|
||||
return createIdentifier("_this", /*location*/ node);
|
||||
}
|
||||
|
||||
@@ -3013,7 +3190,7 @@ namespace ts {
|
||||
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
||||
*/
|
||||
function getLocalName(node: ClassDeclaration | ClassExpression | FunctionDeclaration, allowComments?: boolean, allowSourceMaps?: boolean) {
|
||||
return getDeclarationName(node, allowComments, allowSourceMaps, NodeEmitFlags.LocalName);
|
||||
return getDeclarationName(node, allowComments, allowSourceMaps, EmitFlags.LocalName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3022,18 +3199,18 @@ namespace ts {
|
||||
* @param node The declaration.
|
||||
* @param allowComments Allow comments for the name.
|
||||
*/
|
||||
function getDeclarationName(node: DeclarationStatement | ClassExpression, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags?: NodeEmitFlags) {
|
||||
function getDeclarationName(node: DeclarationStatement | ClassExpression, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags?: EmitFlags) {
|
||||
if (node.name && !isGeneratedIdentifier(node.name)) {
|
||||
const name = getMutableClone(node.name);
|
||||
emitFlags |= getNodeEmitFlags(node.name);
|
||||
emitFlags |= getEmitFlags(node.name);
|
||||
if (!allowSourceMaps) {
|
||||
emitFlags |= NodeEmitFlags.NoSourceMap;
|
||||
emitFlags |= EmitFlags.NoSourceMap;
|
||||
}
|
||||
if (!allowComments) {
|
||||
emitFlags |= NodeEmitFlags.NoComments;
|
||||
emitFlags |= EmitFlags.NoComments;
|
||||
}
|
||||
if (emitFlags) {
|
||||
setNodeEmitFlags(name, emitFlags);
|
||||
setEmitFlags(name, emitFlags);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ namespace ts {
|
||||
return transformSourceFile;
|
||||
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,9 +231,6 @@ namespace ts {
|
||||
endLexicalEnvironment,
|
||||
hoistFunctionDeclaration,
|
||||
hoistVariableDeclaration,
|
||||
setSourceMapRange,
|
||||
setCommentRange,
|
||||
setNodeEmitFlags
|
||||
} = context;
|
||||
|
||||
const compilerOptions = context.getCompilerOptions();
|
||||
@@ -294,6 +291,10 @@ namespace ts {
|
||||
return transformSourceFile;
|
||||
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (node.transformFlags & TransformFlags.ContainsGenerator) {
|
||||
currentSourceFile = node;
|
||||
node = visitEachChild(node, visitor, context);
|
||||
@@ -444,7 +445,7 @@ namespace ts {
|
||||
*/
|
||||
function visitFunctionDeclaration(node: FunctionDeclaration): Statement {
|
||||
// Currently, we only support generators that were originally async functions.
|
||||
if (node.asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) {
|
||||
if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) {
|
||||
node = setOriginalNode(
|
||||
createFunctionDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
@@ -492,7 +493,7 @@ namespace ts {
|
||||
*/
|
||||
function visitFunctionExpression(node: FunctionExpression): Expression {
|
||||
// Currently, we only support generators that were originally async functions.
|
||||
if (node.asteriskToken && node.emitFlags & NodeEmitFlags.AsyncFunctionBody) {
|
||||
if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) {
|
||||
node = setOriginalNode(
|
||||
createFunctionExpression(
|
||||
/*asteriskToken*/ undefined,
|
||||
@@ -616,7 +617,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// Do not hoist custom prologues.
|
||||
if (node.emitFlags & NodeEmitFlags.CustomPrologue) {
|
||||
if (getEmitFlags(node) & EmitFlags.CustomPrologue) {
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -1887,9 +1888,9 @@ namespace ts {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function onSubstituteNode(node: Node, isExpression: boolean): Node {
|
||||
node = previousOnSubstituteNode(node, isExpression);
|
||||
if (isExpression) {
|
||||
function onSubstituteNode(emitContext: EmitContext, node: Node): Node {
|
||||
node = previousOnSubstituteNode(emitContext, node);
|
||||
if (emitContext === EmitContext.Expression) {
|
||||
return substituteExpression(<Expression>node);
|
||||
}
|
||||
return node;
|
||||
@@ -2585,7 +2586,7 @@ namespace ts {
|
||||
/*typeArguments*/ undefined,
|
||||
[
|
||||
createThis(),
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createFunctionExpression(
|
||||
/*asteriskToken*/ undefined,
|
||||
/*name*/ undefined,
|
||||
@@ -2598,7 +2599,7 @@ namespace ts {
|
||||
/*multiLine*/ buildResult.length > 0
|
||||
)
|
||||
),
|
||||
NodeEmitFlags.ReuseTempVariableScope
|
||||
EmitFlags.ReuseTempVariableScope
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace ts {
|
||||
* @param node A SourceFile node.
|
||||
*/
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
currentSourceFile = node;
|
||||
node = visitEachChild(node, visitor, context);
|
||||
currentSourceFile = undefined;
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace ts {
|
||||
return transformSourceFile;
|
||||
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (isExternalModule(node) || compilerOptions.isolatedModules) {
|
||||
currentSourceFile = node;
|
||||
return visitEachChild(node, visitor, context);
|
||||
|
||||
@@ -15,9 +15,6 @@ namespace ts {
|
||||
startLexicalEnvironment,
|
||||
endLexicalEnvironment,
|
||||
hoistVariableDeclaration,
|
||||
setNodeEmitFlags,
|
||||
getNodeEmitFlags,
|
||||
setSourceMapRange,
|
||||
} = context;
|
||||
|
||||
const compilerOptions = context.getCompilerOptions();
|
||||
@@ -54,6 +51,10 @@ namespace ts {
|
||||
* @param node The SourceFile node.
|
||||
*/
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (isExternalModule(node) || compilerOptions.isolatedModules) {
|
||||
currentSourceFile = node;
|
||||
|
||||
@@ -92,7 +93,7 @@ namespace ts {
|
||||
|
||||
const updated = updateSourceFile(node, statements);
|
||||
if (hasExportStarsToExportValues) {
|
||||
setNodeEmitFlags(updated, NodeEmitFlags.EmitExportStar | getNodeEmitFlags(node));
|
||||
setEmitFlags(updated, EmitFlags.EmitExportStar | getEmitFlags(node));
|
||||
}
|
||||
|
||||
return updated;
|
||||
@@ -116,7 +117,7 @@ namespace ts {
|
||||
*/
|
||||
function transformUMDModule(node: SourceFile) {
|
||||
const define = createIdentifier("define");
|
||||
setNodeEmitFlags(define, NodeEmitFlags.UMDDefine);
|
||||
setEmitFlags(define, EmitFlags.UMDDefine);
|
||||
return transformAsynchronousModule(node, define, /*moduleName*/ undefined, /*includeNonAmdDependencies*/ false);
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ namespace ts {
|
||||
if (hasExportStarsToExportValues) {
|
||||
// If we have any `export * from ...` declarations
|
||||
// we need to inform the emitter to add the __export helper.
|
||||
setNodeEmitFlags(body, NodeEmitFlags.EmitExportStar);
|
||||
setEmitFlags(body, EmitFlags.EmitExportStar);
|
||||
}
|
||||
|
||||
return body;
|
||||
@@ -234,7 +235,7 @@ namespace ts {
|
||||
/*location*/ exportEquals
|
||||
);
|
||||
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoTokenSourceMaps | NodeEmitFlags.NoComments);
|
||||
setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments);
|
||||
statements.push(statement);
|
||||
}
|
||||
else {
|
||||
@@ -249,7 +250,7 @@ namespace ts {
|
||||
/*location*/ exportEquals
|
||||
);
|
||||
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(statement, EmitFlags.NoComments);
|
||||
statements.push(statement);
|
||||
}
|
||||
}
|
||||
@@ -388,7 +389,7 @@ namespace ts {
|
||||
|
||||
// Set emitFlags on the name of the importEqualsDeclaration
|
||||
// This is so the printer will not substitute the identifier
|
||||
setNodeEmitFlags(node.name, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(node.name, EmitFlags.NoSubstitution);
|
||||
const statements: Statement[] = [];
|
||||
if (moduleKind !== ModuleKind.AMD) {
|
||||
if (hasModifier(node, ModifierFlags.Export)) {
|
||||
@@ -598,7 +599,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
statements.push(
|
||||
createExportStatement(node.name, setNodeEmitFlags(getSynthesizedClone(node.name), NodeEmitFlags.LocalName), /*location*/ node)
|
||||
createExportStatement(node.name, setEmitFlags(getSynthesizedClone(node.name), EmitFlags.LocalName), /*location*/ node)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -813,7 +814,7 @@ namespace ts {
|
||||
)],
|
||||
/*location*/ node
|
||||
);
|
||||
setNodeEmitFlags(transformedStatement, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(transformedStatement, EmitFlags.NoComments);
|
||||
statements.push(transformedStatement);
|
||||
}
|
||||
|
||||
@@ -821,14 +822,14 @@ namespace ts {
|
||||
return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node);
|
||||
}
|
||||
|
||||
function onEmitNode(node: Node, emit: (node: Node) => void): void {
|
||||
function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void {
|
||||
if (node.kind === SyntaxKind.SourceFile) {
|
||||
bindingNameExportSpecifiersMap = bindingNameExportSpecifiersForFileMap[getOriginalNodeId(node)];
|
||||
previousOnEmitNode(node, emit);
|
||||
previousOnEmitNode(emitContext, node, emitCallback);
|
||||
bindingNameExportSpecifiersMap = undefined;
|
||||
}
|
||||
else {
|
||||
previousOnEmitNode(node, emit);
|
||||
previousOnEmitNode(emitContext, node, emitCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,9 +840,9 @@ namespace ts {
|
||||
* @param isExpression A value indicating whether the node is to be used in an expression
|
||||
* position.
|
||||
*/
|
||||
function onSubstituteNode(node: Node, isExpression: boolean) {
|
||||
node = previousOnSubstituteNode(node, isExpression);
|
||||
if (isExpression) {
|
||||
function onSubstituteNode(emitContext: EmitContext, node: Node) {
|
||||
node = previousOnSubstituteNode(emitContext, node);
|
||||
if (emitContext === EmitContext.Expression) {
|
||||
return substituteExpression(<Expression>node);
|
||||
}
|
||||
else if (isShorthandPropertyAssignment(node)) {
|
||||
@@ -890,7 +891,7 @@ namespace ts {
|
||||
// If the left-hand-side of the binaryExpression is an identifier and its is export through export Specifier
|
||||
if (isIdentifier(left) && isAssignmentOperator(node.operatorToken.kind)) {
|
||||
if (bindingNameExportSpecifiersMap && hasProperty(bindingNameExportSpecifiersMap, left.text)) {
|
||||
setNodeEmitFlags(node, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(node, EmitFlags.NoSubstitution);
|
||||
let nestedExportAssignment: BinaryExpression;
|
||||
for (const specifier of bindingNameExportSpecifiersMap[left.text]) {
|
||||
nestedExportAssignment = nestedExportAssignment ?
|
||||
@@ -910,7 +911,7 @@ namespace ts {
|
||||
const operand = node.operand;
|
||||
if (isIdentifier(operand) && bindingNameExportSpecifiersForFileMap) {
|
||||
if (bindingNameExportSpecifiersMap && hasProperty(bindingNameExportSpecifiersMap, operand.text)) {
|
||||
setNodeEmitFlags(node, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(node, EmitFlags.NoSubstitution);
|
||||
let transformedUnaryExpression: BinaryExpression;
|
||||
if (node.kind === SyntaxKind.PostfixUnaryExpression) {
|
||||
transformedUnaryExpression = createBinary(
|
||||
@@ -920,7 +921,7 @@ namespace ts {
|
||||
/*location*/ node
|
||||
);
|
||||
// We have to set no substitution flag here to prevent visit the binary expression and substitute it again as we will preform all necessary substitution in here
|
||||
setNodeEmitFlags(transformedUnaryExpression, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(transformedUnaryExpression, EmitFlags.NoSubstitution);
|
||||
}
|
||||
let nestedExportAssignment: BinaryExpression;
|
||||
for (const specifier of bindingNameExportSpecifiersMap[operand.text]) {
|
||||
@@ -935,9 +936,9 @@ namespace ts {
|
||||
}
|
||||
|
||||
function trySubstituteExportedName(node: Identifier) {
|
||||
const emitFlags = getNodeEmitFlags(node);
|
||||
if ((emitFlags & NodeEmitFlags.LocalName) === 0) {
|
||||
const container = resolver.getReferencedExportContainer(node, (emitFlags & NodeEmitFlags.ExportName) !== 0);
|
||||
const emitFlags = getEmitFlags(node);
|
||||
if ((emitFlags & EmitFlags.LocalName) === 0) {
|
||||
const container = resolver.getReferencedExportContainer(node, (emitFlags & EmitFlags.ExportName) !== 0);
|
||||
if (container) {
|
||||
if (container.kind === SyntaxKind.SourceFile) {
|
||||
return createPropertyAccess(
|
||||
@@ -953,7 +954,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function trySubstituteImportedName(node: Identifier): Expression {
|
||||
if ((getNodeEmitFlags(node) & NodeEmitFlags.LocalName) === 0) {
|
||||
if ((getEmitFlags(node) & EmitFlags.LocalName) === 0) {
|
||||
const declaration = resolver.getReferencedImportDeclaration(node);
|
||||
if (declaration) {
|
||||
if (isImportClause(declaration)) {
|
||||
@@ -1077,7 +1078,7 @@ namespace ts {
|
||||
if (includeNonAmdDependencies && importAliasName) {
|
||||
// Set emitFlags on the name of the classDeclaration
|
||||
// This is so that when printer will not substitute the identifier
|
||||
setNodeEmitFlags(importAliasName, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(importAliasName, EmitFlags.NoSubstitution);
|
||||
aliasedModuleNames.push(externalModuleName);
|
||||
importAliasNames.push(createParameter(importAliasName));
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace ts {
|
||||
}
|
||||
|
||||
const {
|
||||
getNodeEmitFlags,
|
||||
setNodeEmitFlags,
|
||||
startLexicalEnvironment,
|
||||
endLexicalEnvironment,
|
||||
hoistVariableDeclaration,
|
||||
@@ -50,6 +48,10 @@ namespace ts {
|
||||
return transformSourceFile;
|
||||
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (isExternalModule(node) || compilerOptions.isolatedModules) {
|
||||
currentSourceFile = node;
|
||||
currentNode = node;
|
||||
@@ -116,9 +118,9 @@ namespace ts {
|
||||
createParameter(contextObjectForFile)
|
||||
],
|
||||
/*type*/ undefined,
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createBlock(statements, /*location*/ undefined, /*multiLine*/ true),
|
||||
NodeEmitFlags.EmitEmitHelpers
|
||||
EmitFlags.EmitEmitHelpers
|
||||
)
|
||||
);
|
||||
|
||||
@@ -135,7 +137,7 @@ namespace ts {
|
||||
: [dependencies, body]
|
||||
)
|
||||
)
|
||||
], /*nodeEmitFlags*/ ~NodeEmitFlags.EmitEmitHelpers & getNodeEmitFlags(node));
|
||||
], /*nodeEmitFlags*/ ~EmitFlags.EmitEmitHelpers & getEmitFlags(node));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -984,14 +986,14 @@ namespace ts {
|
||||
// Substitutions
|
||||
//
|
||||
|
||||
function onEmitNode(node: Node, emit: (node: Node) => void): void {
|
||||
function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void {
|
||||
if (node.kind === SyntaxKind.SourceFile) {
|
||||
exportFunctionForFile = exportFunctionForFileMap[getOriginalNodeId(node)];
|
||||
previousOnEmitNode(node, emit);
|
||||
previousOnEmitNode(emitContext, node, emitCallback);
|
||||
exportFunctionForFile = undefined;
|
||||
}
|
||||
else {
|
||||
previousOnEmitNode(node, emit);
|
||||
previousOnEmitNode(emitContext, node, emitCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,9 +1004,9 @@ namespace ts {
|
||||
* @param isExpression A value indicating whether the node is to be used in an expression
|
||||
* position.
|
||||
*/
|
||||
function onSubstituteNode(node: Node, isExpression: boolean) {
|
||||
node = previousOnSubstituteNode(node, isExpression);
|
||||
if (isExpression) {
|
||||
function onSubstituteNode(emitContext: EmitContext, node: Node) {
|
||||
node = previousOnSubstituteNode(emitContext, node);
|
||||
if (emitContext === EmitContext.Expression) {
|
||||
return substituteExpression(<Expression>node);
|
||||
}
|
||||
|
||||
@@ -1053,7 +1055,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function substituteAssignmentExpression(node: BinaryExpression): Expression {
|
||||
setNodeEmitFlags(node, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(node, EmitFlags.NoSubstitution);
|
||||
|
||||
const left = node.left;
|
||||
switch (left.kind) {
|
||||
@@ -1176,7 +1178,7 @@ namespace ts {
|
||||
const exportDeclaration = resolver.getReferencedExportContainer(<Identifier>operand);
|
||||
if (exportDeclaration) {
|
||||
const expr = createPrefix(node.operator, operand, node);
|
||||
setNodeEmitFlags(expr, NodeEmitFlags.NoSubstitution);
|
||||
setEmitFlags(expr, EmitFlags.NoSubstitution);
|
||||
const call = createExportExpression(<Identifier>operand, expr);
|
||||
if (node.kind === SyntaxKind.PrefixUnaryExpression) {
|
||||
return call;
|
||||
@@ -1241,7 +1243,7 @@ namespace ts {
|
||||
]),
|
||||
m,
|
||||
createBlock([
|
||||
setNodeEmitFlags(
|
||||
setEmitFlags(
|
||||
createIf(
|
||||
condition,
|
||||
createStatement(
|
||||
@@ -1251,7 +1253,7 @@ namespace ts {
|
||||
)
|
||||
)
|
||||
),
|
||||
NodeEmitFlags.SingleLine
|
||||
EmitFlags.SingleLine
|
||||
)
|
||||
])
|
||||
),
|
||||
@@ -1393,10 +1395,10 @@ namespace ts {
|
||||
hoistBindingElement(node, /*isExported*/ false);
|
||||
}
|
||||
|
||||
function updateSourceFile(node: SourceFile, statements: Statement[], nodeEmitFlags: NodeEmitFlags) {
|
||||
function updateSourceFile(node: SourceFile, statements: Statement[], nodeEmitFlags: EmitFlags) {
|
||||
const updated = getMutableClone(node);
|
||||
updated.statements = createNodeArray(statements, node.statements);
|
||||
setNodeEmitFlags(updated, nodeEmitFlags);
|
||||
setEmitFlags(updated, nodeEmitFlags);
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
+150
-67
@@ -24,10 +24,6 @@ namespace ts {
|
||||
|
||||
export function transformTypeScript(context: TransformationContext) {
|
||||
const {
|
||||
getNodeEmitFlags,
|
||||
setNodeEmitFlags,
|
||||
setCommentRange,
|
||||
setSourceMapRange,
|
||||
startLexicalEnvironment,
|
||||
endLexicalEnvironment,
|
||||
hoistVariableDeclaration,
|
||||
@@ -46,11 +42,16 @@ namespace ts {
|
||||
context.onEmitNode = onEmitNode;
|
||||
context.onSubstituteNode = onSubstituteNode;
|
||||
|
||||
// Enable substitution for property/element access to emit const enum values.
|
||||
context.enableSubstitution(SyntaxKind.PropertyAccessExpression);
|
||||
context.enableSubstitution(SyntaxKind.ElementAccessExpression);
|
||||
|
||||
// These variables contain state that changes as we descend into the tree.
|
||||
let currentSourceFile: SourceFile;
|
||||
let currentNamespace: ModuleDeclaration;
|
||||
let currentNamespaceContainerName: Identifier;
|
||||
let currentScope: SourceFile | Block | ModuleBlock | CaseBlock;
|
||||
let currentScopeFirstDeclarationsOfName: Map<Node>;
|
||||
let currentSourceFileExternalHelpersModuleName: Identifier;
|
||||
|
||||
/**
|
||||
@@ -85,6 +86,10 @@ namespace ts {
|
||||
* @param node A SourceFile node.
|
||||
*/
|
||||
function transformSourceFile(node: SourceFile) {
|
||||
if (isDeclarationFile(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return visitNode(node, visitor, isSourceFile);
|
||||
}
|
||||
|
||||
@@ -96,6 +101,7 @@ namespace ts {
|
||||
function saveStateAndInvoke<T>(node: Node, f: (node: Node) => T): T {
|
||||
// Save state
|
||||
const savedCurrentScope = currentScope;
|
||||
const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName;
|
||||
|
||||
// Handle state changes before visiting a node.
|
||||
onBeforeVisitNode(node);
|
||||
@@ -103,8 +109,11 @@ namespace ts {
|
||||
const visited = f(node);
|
||||
|
||||
// Restore state
|
||||
currentScope = savedCurrentScope;
|
||||
if (currentScope !== savedCurrentScope) {
|
||||
currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName;
|
||||
}
|
||||
|
||||
currentScope = savedCurrentScope;
|
||||
return visited;
|
||||
}
|
||||
|
||||
@@ -410,6 +419,16 @@ namespace ts {
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.Block:
|
||||
currentScope = <SourceFile | CaseBlock | ModuleBlock | Block>node;
|
||||
currentScopeFirstDeclarationsOfName = undefined;
|
||||
break;
|
||||
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
if (hasModifier(node, ModifierFlags.Ambient)) {
|
||||
break;
|
||||
}
|
||||
|
||||
recordEmittedDeclarationInScope(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -449,7 +468,7 @@ namespace ts {
|
||||
node = visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
setNodeEmitFlags(node, NodeEmitFlags.EmitEmitHelpers | node.emitFlags);
|
||||
setEmitFlags(node, EmitFlags.EmitEmitHelpers | getEmitFlags(node));
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -521,7 +540,7 @@ namespace ts {
|
||||
// To better align with the old emitter, we should not emit a trailing source map
|
||||
// entry if the class has static properties.
|
||||
if (staticProperties.length > 0) {
|
||||
setNodeEmitFlags(classDeclaration, NodeEmitFlags.NoTrailingSourceMap | getNodeEmitFlags(classDeclaration));
|
||||
setEmitFlags(classDeclaration, EmitFlags.NoTrailingSourceMap | getEmitFlags(classDeclaration));
|
||||
}
|
||||
|
||||
statements.push(classDeclaration);
|
||||
@@ -776,7 +795,7 @@ namespace ts {
|
||||
|
||||
// To preserve the behavior of the old emitter, we explicitly indent
|
||||
// the body of a class with static initializers.
|
||||
setNodeEmitFlags(classExpression, NodeEmitFlags.Indented | getNodeEmitFlags(classExpression));
|
||||
setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression));
|
||||
expressions.push(startOnNewLine(createAssignment(temp, classExpression)));
|
||||
addRange(expressions, generateInitializedPropertyExpressions(node, staticProperties, temp));
|
||||
expressions.push(startOnNewLine(temp));
|
||||
@@ -1009,10 +1028,10 @@ namespace ts {
|
||||
Debug.assert(isIdentifier(node.name));
|
||||
const name = node.name as Identifier;
|
||||
const propertyName = getMutableClone(name);
|
||||
setNodeEmitFlags(propertyName, NodeEmitFlags.NoComments | NodeEmitFlags.NoSourceMap);
|
||||
setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap);
|
||||
|
||||
const localName = getMutableClone(name);
|
||||
setNodeEmitFlags(localName, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(localName, EmitFlags.NoComments);
|
||||
|
||||
return startOnNewLine(
|
||||
createStatement(
|
||||
@@ -1418,7 +1437,7 @@ namespace ts {
|
||||
moveRangePastDecorators(member)
|
||||
);
|
||||
|
||||
setNodeEmitFlags(helper, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(helper, EmitFlags.NoComments);
|
||||
return helper;
|
||||
}
|
||||
|
||||
@@ -1467,7 +1486,7 @@ namespace ts {
|
||||
);
|
||||
|
||||
const result = createAssignment(getDeclarationName(node), expression, moveRangePastDecorators(node));
|
||||
setNodeEmitFlags(result, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(result, EmitFlags.NoComments);
|
||||
return result;
|
||||
}
|
||||
// Emit the call to __decorate. Given the class:
|
||||
@@ -1491,7 +1510,7 @@ namespace ts {
|
||||
moveRangePastDecorators(node)
|
||||
);
|
||||
|
||||
setNodeEmitFlags(result, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(result, EmitFlags.NoComments);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1521,7 +1540,7 @@ namespace ts {
|
||||
transformDecorator(decorator),
|
||||
parameterOffset,
|
||||
/*location*/ decorator.expression);
|
||||
setNodeEmitFlags(helper, NodeEmitFlags.NoComments);
|
||||
setEmitFlags(helper, EmitFlags.NoComments);
|
||||
expressions.push(helper);
|
||||
}
|
||||
}
|
||||
@@ -2324,11 +2343,11 @@ namespace ts {
|
||||
if (languageVersion >= ScriptTarget.ES6) {
|
||||
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
|
||||
enableSubstitutionForAsyncMethodsWithSuper();
|
||||
setNodeEmitFlags(block, NodeEmitFlags.EmitAdvancedSuperHelper);
|
||||
setEmitFlags(block, EmitFlags.EmitAdvancedSuperHelper);
|
||||
}
|
||||
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
|
||||
enableSubstitutionForAsyncMethodsWithSuper();
|
||||
setNodeEmitFlags(block, NodeEmitFlags.EmitSuperHelper);
|
||||
setEmitFlags(block, EmitFlags.EmitSuperHelper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2375,7 +2394,7 @@ namespace ts {
|
||||
setOriginalNode(parameter, node);
|
||||
setCommentRange(parameter, node);
|
||||
setSourceMapRange(parameter, moveRangePastModifiers(node));
|
||||
setNodeEmitFlags(parameter.name, NodeEmitFlags.NoTrailingSourceMap);
|
||||
setEmitFlags(parameter.name, EmitFlags.NoTrailingSourceMap);
|
||||
|
||||
return parameter;
|
||||
}
|
||||
@@ -2498,10 +2517,12 @@ namespace ts {
|
||||
}
|
||||
|
||||
function shouldEmitVarForEnumDeclaration(node: EnumDeclaration | ModuleDeclaration) {
|
||||
return !hasModifier(node, ModifierFlags.Export)
|
||||
|| (isES6ExportedDeclaration(node) && isFirstDeclarationOfKind(node, node.kind));
|
||||
return isFirstEmittedDeclarationInScope(node)
|
||||
&& (!hasModifier(node, ModifierFlags.Export)
|
||||
|| isES6ExportedDeclaration(node));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Adds a trailing VariableStatement for an enum or module declaration.
|
||||
*/
|
||||
@@ -2534,17 +2555,18 @@ namespace ts {
|
||||
|
||||
// We request to be advised when the printer is about to print this node. This allows
|
||||
// us to set up the correct state for later substitutions.
|
||||
let emitFlags = NodeEmitFlags.AdviseOnEmitNode;
|
||||
let emitFlags = EmitFlags.AdviseOnEmitNode;
|
||||
|
||||
// If needed, we should emit a variable declaration for the enum. If we emit
|
||||
// a leading variable declaration, we should not emit leading comments for the
|
||||
// enum body.
|
||||
recordEmittedDeclarationInScope(node);
|
||||
if (shouldEmitVarForEnumDeclaration(node)) {
|
||||
addVarForEnumOrModuleDeclaration(statements, node);
|
||||
|
||||
// We should still emit the comments if we are emitting a system module.
|
||||
if (moduleKind !== ModuleKind.System || currentScope !== currentSourceFile) {
|
||||
emitFlags |= NodeEmitFlags.NoLeadingComments;
|
||||
emitFlags |= EmitFlags.NoLeadingComments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2584,7 +2606,7 @@ namespace ts {
|
||||
);
|
||||
|
||||
setOriginalNode(enumStatement, node);
|
||||
setNodeEmitFlags(enumStatement, emitFlags);
|
||||
setEmitFlags(enumStatement, emitFlags);
|
||||
statements.push(enumStatement);
|
||||
|
||||
if (isNamespaceExport(node)) {
|
||||
@@ -2675,20 +2697,48 @@ namespace ts {
|
||||
return isInstantiatedModule(node, compilerOptions.preserveConstEnums || compilerOptions.isolatedModules);
|
||||
}
|
||||
|
||||
function isModuleMergedWithES6Class(node: ModuleDeclaration) {
|
||||
return languageVersion === ScriptTarget.ES6
|
||||
&& isMergedWithClass(node);
|
||||
}
|
||||
|
||||
function isES6ExportedDeclaration(node: Node) {
|
||||
return isExternalModuleExport(node)
|
||||
&& moduleKind === ModuleKind.ES6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records that a declaration was emitted in the current scope, if it was the first
|
||||
* declaration for the provided symbol.
|
||||
*
|
||||
* NOTE: if there is ever a transformation above this one, we may not be able to rely
|
||||
* on symbol names.
|
||||
*/
|
||||
function recordEmittedDeclarationInScope(node: Node) {
|
||||
const name = node.symbol && node.symbol.name;
|
||||
if (name) {
|
||||
if (!currentScopeFirstDeclarationsOfName) {
|
||||
currentScopeFirstDeclarationsOfName = createMap<Node>();
|
||||
}
|
||||
|
||||
if (!(name in currentScopeFirstDeclarationsOfName)) {
|
||||
currentScopeFirstDeclarationsOfName[name] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a declaration is the first declaration with the same name emitted
|
||||
* in the current scope.
|
||||
*/
|
||||
function isFirstEmittedDeclarationInScope(node: Node) {
|
||||
if (currentScopeFirstDeclarationsOfName) {
|
||||
const name = node.symbol && node.symbol.name;
|
||||
if (name) {
|
||||
return currentScopeFirstDeclarationsOfName[name] === node;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldEmitVarForModuleDeclaration(node: ModuleDeclaration) {
|
||||
return !isModuleMergedWithES6Class(node)
|
||||
&& (!isES6ExportedDeclaration(node)
|
||||
|| isFirstDeclarationOfKind(node, node.kind));
|
||||
return isFirstEmittedDeclarationInScope(node);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2736,7 +2786,7 @@ namespace ts {
|
||||
// })(m1 || (m1 = {})); // trailing comment module
|
||||
//
|
||||
setCommentRange(statement, node);
|
||||
setNodeEmitFlags(statement, NodeEmitFlags.NoTrailingComments);
|
||||
setEmitFlags(statement, EmitFlags.NoTrailingComments);
|
||||
statements.push(statement);
|
||||
}
|
||||
|
||||
@@ -2759,16 +2809,17 @@ namespace ts {
|
||||
|
||||
// We request to be advised when the printer is about to print this node. This allows
|
||||
// us to set up the correct state for later substitutions.
|
||||
let emitFlags = NodeEmitFlags.AdviseOnEmitNode;
|
||||
let emitFlags = EmitFlags.AdviseOnEmitNode;
|
||||
|
||||
// If needed, we should emit a variable declaration for the module. If we emit
|
||||
// a leading variable declaration, we should not emit leading comments for the
|
||||
// module body.
|
||||
recordEmittedDeclarationInScope(node);
|
||||
if (shouldEmitVarForModuleDeclaration(node)) {
|
||||
addVarForEnumOrModuleDeclaration(statements, node);
|
||||
// We should still emit the comments if we are emitting a system module.
|
||||
if (moduleKind !== ModuleKind.System || currentScope !== currentSourceFile) {
|
||||
emitFlags |= NodeEmitFlags.NoLeadingComments;
|
||||
emitFlags |= EmitFlags.NoLeadingComments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2820,7 +2871,7 @@ namespace ts {
|
||||
);
|
||||
|
||||
setOriginalNode(moduleStatement, node);
|
||||
setNodeEmitFlags(moduleStatement, emitFlags);
|
||||
setEmitFlags(moduleStatement, emitFlags);
|
||||
statements.push(moduleStatement);
|
||||
return statements;
|
||||
}
|
||||
@@ -2833,8 +2884,10 @@ namespace ts {
|
||||
function transformModuleBody(node: ModuleDeclaration, namespaceLocalName: Identifier): Block {
|
||||
const savedCurrentNamespaceContainerName = currentNamespaceContainerName;
|
||||
const savedCurrentNamespace = currentNamespace;
|
||||
const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName;
|
||||
currentNamespaceContainerName = namespaceLocalName;
|
||||
currentNamespace = node;
|
||||
currentScopeFirstDeclarationsOfName = undefined;
|
||||
|
||||
const statements: Statement[] = [];
|
||||
startLexicalEnvironment();
|
||||
@@ -2861,10 +2914,12 @@ namespace ts {
|
||||
const moduleBlock = <ModuleBlock>getInnerMostModuleDeclarationFromDottedModule(node).body;
|
||||
statementsLocation = moveRangePos(moduleBlock.statements, -1);
|
||||
}
|
||||
addRange(statements, endLexicalEnvironment());
|
||||
|
||||
addRange(statements, endLexicalEnvironment());
|
||||
currentNamespaceContainerName = savedCurrentNamespaceContainerName;
|
||||
currentNamespace = savedCurrentNamespace;
|
||||
currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName;
|
||||
|
||||
const block = createBlock(
|
||||
createNodeArray(
|
||||
statements,
|
||||
@@ -2895,7 +2950,7 @@ namespace ts {
|
||||
// })(hello || (hello = {}));
|
||||
// We only want to emit comment on the namespace which contains block body itself, not the containing namespaces.
|
||||
if (body.kind !== SyntaxKind.ModuleBlock) {
|
||||
setNodeEmitFlags(block, block.emitFlags | NodeEmitFlags.NoComments);
|
||||
setEmitFlags(block, getEmitFlags(block) | EmitFlags.NoComments);
|
||||
}
|
||||
return block;
|
||||
}
|
||||
@@ -2936,7 +2991,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const moduleReference = createExpressionFromEntityName(<EntityName>node.moduleReference);
|
||||
setNodeEmitFlags(moduleReference, NodeEmitFlags.NoComments | NodeEmitFlags.NoNestedComments);
|
||||
setEmitFlags(moduleReference, EmitFlags.NoComments | EmitFlags.NoNestedComments);
|
||||
|
||||
if (isNamedExternalModuleExport(node) || !isNamespaceExport(node)) {
|
||||
// export var ${name} = ${moduleReference};
|
||||
@@ -3048,15 +3103,15 @@ namespace ts {
|
||||
|
||||
function getNamespaceMemberName(name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): Expression {
|
||||
const qualifiedName = createPropertyAccess(currentNamespaceContainerName, getSynthesizedClone(name), /*location*/ name);
|
||||
let emitFlags: NodeEmitFlags;
|
||||
let emitFlags: EmitFlags;
|
||||
if (!allowComments) {
|
||||
emitFlags |= NodeEmitFlags.NoComments;
|
||||
emitFlags |= EmitFlags.NoComments;
|
||||
}
|
||||
if (!allowSourceMaps) {
|
||||
emitFlags |= NodeEmitFlags.NoSourceMap;
|
||||
emitFlags |= EmitFlags.NoSourceMap;
|
||||
}
|
||||
if (emitFlags) {
|
||||
setNodeEmitFlags(qualifiedName, emitFlags);
|
||||
setEmitFlags(qualifiedName, emitFlags);
|
||||
}
|
||||
return qualifiedName;
|
||||
}
|
||||
@@ -3093,7 +3148,7 @@ namespace ts {
|
||||
* @param allowComments A value indicating whether comments may be emitted for the name.
|
||||
*/
|
||||
function getLocalName(node: DeclarationStatement | ClassExpression, noSourceMaps?: boolean, allowComments?: boolean) {
|
||||
return getDeclarationName(node, allowComments, !noSourceMaps, NodeEmitFlags.LocalName);
|
||||
return getDeclarationName(node, allowComments, !noSourceMaps, EmitFlags.LocalName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3111,7 +3166,7 @@ namespace ts {
|
||||
return getNamespaceMemberName(getDeclarationName(node), allowComments, !noSourceMaps);
|
||||
}
|
||||
|
||||
return getDeclarationName(node, allowComments, !noSourceMaps, NodeEmitFlags.ExportName);
|
||||
return getDeclarationName(node, allowComments, !noSourceMaps, EmitFlags.ExportName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3122,20 +3177,20 @@ namespace ts {
|
||||
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
||||
* @param emitFlags Additional NodeEmitFlags to specify for the name.
|
||||
*/
|
||||
function getDeclarationName(node: DeclarationStatement | ClassExpression, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags?: NodeEmitFlags) {
|
||||
function getDeclarationName(node: DeclarationStatement | ClassExpression, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags?: EmitFlags) {
|
||||
if (node.name) {
|
||||
const name = getMutableClone(node.name);
|
||||
emitFlags |= getNodeEmitFlags(node.name);
|
||||
emitFlags |= getEmitFlags(node.name);
|
||||
if (!allowSourceMaps) {
|
||||
emitFlags |= NodeEmitFlags.NoSourceMap;
|
||||
emitFlags |= EmitFlags.NoSourceMap;
|
||||
}
|
||||
|
||||
if (!allowComments) {
|
||||
emitFlags |= NodeEmitFlags.NoComments;
|
||||
emitFlags |= EmitFlags.NoComments;
|
||||
}
|
||||
|
||||
if (emitFlags) {
|
||||
setNodeEmitFlags(name, emitFlags);
|
||||
setEmitFlags(name, emitFlags);
|
||||
}
|
||||
|
||||
return name;
|
||||
@@ -3231,7 +3286,7 @@ namespace ts {
|
||||
* @param node The node to emit.
|
||||
* @param emit A callback used to emit the node in the printer.
|
||||
*/
|
||||
function onEmitNode(node: Node, emit: (node: Node) => void): void {
|
||||
function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void {
|
||||
const savedApplicableSubstitutions = applicableSubstitutions;
|
||||
const savedCurrentSuperContainer = currentSuperContainer;
|
||||
// If we need to support substitutions for `super` in an async method,
|
||||
@@ -3248,7 +3303,7 @@ namespace ts {
|
||||
applicableSubstitutions |= TypeScriptSubstitutionFlags.NonQualifiedEnumMembers;
|
||||
}
|
||||
|
||||
previousOnEmitNode(node, emit);
|
||||
previousOnEmitNode(emitContext, node, emitCallback);
|
||||
|
||||
applicableSubstitutions = savedApplicableSubstitutions;
|
||||
currentSuperContainer = savedCurrentSuperContainer;
|
||||
@@ -3261,9 +3316,9 @@ namespace ts {
|
||||
* @param isExpression A value indicating whether the node is to be used in an expression
|
||||
* position.
|
||||
*/
|
||||
function onSubstituteNode(node: Node, isExpression: boolean) {
|
||||
node = previousOnSubstituteNode(node, isExpression);
|
||||
if (isExpression) {
|
||||
function onSubstituteNode(emitContext: EmitContext, node: Node) {
|
||||
node = previousOnSubstituteNode(emitContext, node);
|
||||
if (emitContext === EmitContext.Expression) {
|
||||
return substituteExpression(<Expression>node);
|
||||
}
|
||||
else if (isShorthandPropertyAssignment(node)) {
|
||||
@@ -3294,17 +3349,15 @@ namespace ts {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
return substituteExpressionIdentifier(<Identifier>node);
|
||||
}
|
||||
|
||||
if (enabledSubstitutions & TypeScriptSubstitutionFlags.AsyncMethodsWithSuper) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
return substitutePropertyAccessExpression(<PropertyAccessExpression>node);
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return substituteElementAccessExpression(<ElementAccessExpression>node);
|
||||
case SyntaxKind.CallExpression:
|
||||
if (enabledSubstitutions & TypeScriptSubstitutionFlags.AsyncMethodsWithSuper) {
|
||||
return substituteCallExpression(<CallExpression>node);
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
return substitutePropertyAccessExpression(<PropertyAccessExpression>node);
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return substituteElementAccessExpression(<ElementAccessExpression>node);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -3342,7 +3395,7 @@ namespace ts {
|
||||
|
||||
function trySubstituteNamespaceExportedName(node: Identifier): Expression {
|
||||
// If this is explicitly a local name, do not substitute.
|
||||
if (enabledSubstitutions & applicableSubstitutions && (getNodeEmitFlags(node) & NodeEmitFlags.LocalName) === 0) {
|
||||
if (enabledSubstitutions & applicableSubstitutions && (getEmitFlags(node) & EmitFlags.LocalName) === 0) {
|
||||
// If we are nested within a namespace declaration, we may need to qualifiy
|
||||
// an identifier that is exported from a merged namespace.
|
||||
const container = resolver.getReferencedExportContainer(node, /*prefixLocals*/ false);
|
||||
@@ -3381,7 +3434,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
|
||||
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
if (enabledSubstitutions & TypeScriptSubstitutionFlags.AsyncMethodsWithSuper && node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
const flags = getSuperContainerAsyncMethodFlags();
|
||||
if (flags) {
|
||||
return createSuperAccessInAsyncMethod(
|
||||
@@ -3392,11 +3445,11 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
return substituteConstantValue(node);
|
||||
}
|
||||
|
||||
function substituteElementAccessExpression(node: ElementAccessExpression) {
|
||||
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
if (enabledSubstitutions & TypeScriptSubstitutionFlags.AsyncMethodsWithSuper && node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
const flags = getSuperContainerAsyncMethodFlags();
|
||||
if (flags) {
|
||||
return createSuperAccessInAsyncMethod(
|
||||
@@ -3407,9 +3460,39 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
return substituteConstantValue(node);
|
||||
}
|
||||
|
||||
function substituteConstantValue(node: PropertyAccessExpression | ElementAccessExpression): LeftHandSideExpression {
|
||||
const constantValue = tryGetConstEnumValue(node);
|
||||
if (constantValue !== undefined) {
|
||||
const substitute = createLiteral(constantValue);
|
||||
setSourceMapRange(substitute, node);
|
||||
setCommentRange(substitute, node);
|
||||
if (!compilerOptions.removeComments) {
|
||||
const propertyName = isPropertyAccessExpression(node)
|
||||
? declarationNameToString(node.name)
|
||||
: getTextOfNode(node.argumentExpression);
|
||||
substitute.trailingComment = ` ${propertyName} `;
|
||||
}
|
||||
|
||||
setConstantValue(node, constantValue);
|
||||
return substitute;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function tryGetConstEnumValue(node: Node): number {
|
||||
if (compilerOptions.isolatedModules) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return isPropertyAccessExpression(node) || isElementAccessExpression(node)
|
||||
? resolver.getConstantValue(<PropertyAccessExpression | ElementAccessExpression>node)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function createSuperAccessInAsyncMethod(argumentExpression: Expression, flags: NodeCheckFlags, location: TextRange): LeftHandSideExpression {
|
||||
if (flags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
|
||||
return createPropertyAccess(
|
||||
|
||||
+25
-6
@@ -19,6 +19,7 @@ namespace ts {
|
||||
remove(fileName: Path): void;
|
||||
|
||||
forEachValue(f: (key: Path, v: T) => void): void;
|
||||
getKeys(): Path[];
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
@@ -495,10 +496,7 @@ namespace ts {
|
||||
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
|
||||
/* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
|
||||
/* @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding)
|
||||
/* @internal */ transformId?: number; // Associates transient transformation properties with a specific transformation (initialized by transformation).
|
||||
/* @internal */ emitFlags?: NodeEmitFlags; // Transient emit flags for a synthesized node (initialized by transformation).
|
||||
/* @internal */ sourceMapRange?: TextRange; // Transient custom sourcemap range for a synthesized node (initialized by transformation).
|
||||
/* @internal */ commentRange?: TextRange; // Transient custom comment range for a synthesized node (initialized by transformation).
|
||||
/* @internal */ emitNode?: EmitNode; // Associated EmitNode (initialized by transforms)
|
||||
}
|
||||
|
||||
export interface NodeArray<T extends Node> extends Array<T>, TextRange {
|
||||
@@ -1874,7 +1872,7 @@ namespace ts {
|
||||
* used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter
|
||||
* will be invoked when writing the JavaScript and declaration files.
|
||||
*/
|
||||
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
|
||||
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean): EmitResult;
|
||||
|
||||
getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[];
|
||||
getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[];
|
||||
@@ -1892,6 +1890,7 @@ namespace ts {
|
||||
// For testing purposes only. Should not be used by any other consumers (including the
|
||||
// language service).
|
||||
/* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker;
|
||||
/* @internal */ dropDiagnosticsProducingTypeChecker(): void;
|
||||
|
||||
/* @internal */ getClassifiableNames(): Map<string>;
|
||||
|
||||
@@ -2063,6 +2062,7 @@ namespace ts {
|
||||
UseFullyQualifiedType = 0x00000080, // Write out the fully qualified type name (eg. Module.Type, instead of Type)
|
||||
InFirstTypeArgument = 0x00000100, // Writing first type argument of the instantiated type
|
||||
InTypeAlias = 0x00000200, // Writing type in type alias declaration
|
||||
UseTypeAliasValue = 0x00000400, // Serialize the type instead of using type-alias. This is needed when we emit declaration file.
|
||||
}
|
||||
|
||||
export const enum SymbolFormatFlags {
|
||||
@@ -2874,6 +2874,7 @@ namespace ts {
|
||||
raw?: any;
|
||||
errors: Diagnostic[];
|
||||
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
export const enum WatchDirectoryFlags {
|
||||
@@ -3197,7 +3198,17 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum NodeEmitFlags {
|
||||
export interface EmitNode {
|
||||
flags?: EmitFlags;
|
||||
commentRange?: TextRange;
|
||||
sourceMapRange?: TextRange;
|
||||
tokenSourceMapRanges?: Map<TextRange>;
|
||||
annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup.
|
||||
constantValue?: number;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum EmitFlags {
|
||||
EmitEmitHelpers = 1 << 0, // Any emit helpers should be written to this node.
|
||||
EmitExportStar = 1 << 1, // The export * helper should be written to this node.
|
||||
EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods.
|
||||
@@ -3227,6 +3238,14 @@ namespace ts {
|
||||
CustomPrologue = 1 << 23, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum EmitContext {
|
||||
SourceFile, // Emitting a SourceFile
|
||||
Expression, // Emitting an Expression
|
||||
IdentifierName, // Emitting an IdentifierName
|
||||
Unspecified, // Emitting an otherwise unspecified node
|
||||
}
|
||||
|
||||
/** Additional context provided to `visitEachChild` */
|
||||
/* @internal */
|
||||
export interface LexicalEnvironment {
|
||||
|
||||
+20
-56
@@ -1,4 +1,4 @@
|
||||
/// <reference path="sys.ts" />
|
||||
/// <reference path="sys.ts" />
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
@@ -83,7 +83,7 @@ namespace ts {
|
||||
return node.end - node.pos;
|
||||
}
|
||||
|
||||
export function arrayIsEqualTo<T>(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean): boolean {
|
||||
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, equaler?: (a: T, b: T) => boolean): boolean {
|
||||
if (!array1 || !array2) {
|
||||
return array1 === array2;
|
||||
}
|
||||
@@ -982,11 +982,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an super call\property node returns a closest node where either
|
||||
* - super call\property is legal in the node and not legal in the parent node the node.
|
||||
* Given an super call/property node, returns the closest node where
|
||||
* - a super call/property access is legal in the node and not legal in the parent node the node.
|
||||
* i.e. super call is legal in constructor but not legal in the class body.
|
||||
* - node is arrow function (so caller might need to call getSuperContainer in case it needs to climb higher)
|
||||
* - super call\property is definitely illegal in the node (but might be legal in some subnode)
|
||||
* - the container is an arrow function (so caller might need to call getSuperContainer again in case it needs to climb higher)
|
||||
* - a super call/property is definitely illegal in the container (but might be legal in some subnode)
|
||||
* i.e. super property access is illegal in function declaration but can be legal in the statement list
|
||||
*/
|
||||
export function getSuperContainer(node: Node, stopOnFunctions: boolean): Node {
|
||||
@@ -1253,12 +1253,6 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isExternalModuleNameRelative(moduleName: string): boolean {
|
||||
// TypeScript 1.0 spec (April 2014): 11.2.1
|
||||
// An external module name is "relative" if the first term is "." or "..".
|
||||
return /^\.\.?($|[\\/])/.test(moduleName);
|
||||
}
|
||||
|
||||
export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) {
|
||||
const moduleState = getModuleInstanceState(node);
|
||||
return moduleState === ModuleInstanceState.Instantiated ||
|
||||
@@ -1949,12 +1943,6 @@ namespace ts {
|
||||
|| positionIsSynthesized(node.end);
|
||||
}
|
||||
|
||||
export function positionIsSynthesized(pos: number): boolean {
|
||||
// This is a fast way of testing the following conditions:
|
||||
// pos === undefined || pos === null || isNaN(pos) || pos < 0;
|
||||
return !(pos >= 0);
|
||||
}
|
||||
|
||||
export function getOriginalNode(node: Node): Node {
|
||||
if (node) {
|
||||
while (node.original !== undefined) {
|
||||
@@ -2507,22 +2495,10 @@ namespace ts {
|
||||
const options = host.getCompilerOptions();
|
||||
const outputDir = options.declarationDir || options.outDir; // Prefer declaration folder if specified
|
||||
|
||||
if (options.declaration) {
|
||||
const path = outputDir
|
||||
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
|
||||
: sourceFile.fileName;
|
||||
return removeFileExtension(path) + ".d.ts";
|
||||
}
|
||||
}
|
||||
|
||||
export function getEmitScriptTarget(compilerOptions: CompilerOptions) {
|
||||
return compilerOptions.target || ScriptTarget.ES3;
|
||||
}
|
||||
|
||||
export function getEmitModuleKind(compilerOptions: CompilerOptions) {
|
||||
return typeof compilerOptions.module === "number" ?
|
||||
compilerOptions.module :
|
||||
getEmitScriptTarget(compilerOptions) === ScriptTarget.ES6 ? ModuleKind.ES6 : ModuleKind.CommonJS;
|
||||
const path = outputDir
|
||||
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
|
||||
: sourceFile.fileName;
|
||||
return removeFileExtension(path) + ".d.ts";
|
||||
}
|
||||
|
||||
export interface EmitFileNames {
|
||||
@@ -2575,7 +2551,8 @@ namespace ts {
|
||||
* @param action The action to execute.
|
||||
*/
|
||||
export function forEachTransformedEmitFile(host: EmitHost, sourceFiles: SourceFile[],
|
||||
action: (jsFilePath: string, sourceMapFilePath: string, declarationFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) => void) {
|
||||
action: (jsFilePath: string, sourceMapFilePath: string, declarationFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) => void,
|
||||
emitOnlyDtsFiles?: boolean) {
|
||||
const options = host.getCompilerOptions();
|
||||
// Emit on each source file
|
||||
if (options.outFile || options.out) {
|
||||
@@ -2608,7 +2585,7 @@ namespace ts {
|
||||
}
|
||||
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, extension);
|
||||
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
|
||||
const declarationFilePath = !isSourceFileJavaScript(sourceFile) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
|
||||
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (options.declaration || emitOnlyDtsFiles) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
|
||||
action(jsFilePath, sourceMapFilePath, declarationFilePath, [sourceFile], /*isBundledEmit*/ false);
|
||||
}
|
||||
|
||||
@@ -2636,8 +2613,9 @@ namespace ts {
|
||||
* @param targetSourceFile An optional target source file to emit.
|
||||
*/
|
||||
export function forEachExpectedEmitFile(host: EmitHost,
|
||||
action: (emitFileNames: EmitFileNames, sourceFiles: SourceFile[], isBundledEmit: boolean) => void,
|
||||
targetSourceFile?: SourceFile) {
|
||||
action: (emitFileNames: EmitFileNames, sourceFiles: SourceFile[], isBundledEmit: boolean, emitOnlyDtsFiles: boolean) => void,
|
||||
targetSourceFile?: SourceFile,
|
||||
emitOnlyDtsFiles?: boolean) {
|
||||
const options = host.getCompilerOptions();
|
||||
// Emit on each source file
|
||||
if (options.outFile || options.out) {
|
||||
@@ -2670,12 +2648,13 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, extension);
|
||||
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
|
||||
const emitFileNames: EmitFileNames = {
|
||||
jsFilePath,
|
||||
sourceMapFilePath: getSourceMapFilePath(jsFilePath, options),
|
||||
declarationFilePath: !isSourceFileJavaScript(sourceFile) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined
|
||||
declarationFilePath
|
||||
};
|
||||
action(emitFileNames, [sourceFile], /*isBundledEmit*/false);
|
||||
action(emitFileNames, [sourceFile], /*isBundledEmit*/false, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
function onBundledEmit(host: EmitHost) {
|
||||
@@ -2693,7 +2672,7 @@ namespace ts {
|
||||
sourceMapFilePath: getSourceMapFilePath(jsFilePath, options),
|
||||
declarationFilePath: options.declaration ? removeFileExtension(jsFilePath) + ".d.ts" : undefined
|
||||
};
|
||||
action(emitFileNames, bundledSources, /*isBundledEmit*/true);
|
||||
action(emitFileNames, bundledSources, /*isBundledEmit*/true, emitOnlyDtsFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3132,19 +3111,10 @@ namespace ts {
|
||||
return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
|
||||
}
|
||||
|
||||
export function hasJavaScriptFileExtension(fileName: string) {
|
||||
return forEach(supportedJavascriptExtensions, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
|
||||
export function hasTypeScriptFileExtension(fileName: string) {
|
||||
return forEach(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
|
||||
/** Return ".ts", ".d.ts", or ".tsx", if that is the extension. */
|
||||
export function tryExtractTypeScriptExtension(fileName: string): string | undefined {
|
||||
return find(supportedTypescriptExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
|
||||
* representing the UTF-8 encoding of the character, and return the expanded char code list.
|
||||
@@ -3270,12 +3240,6 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string {
|
||||
return !isRootedDiskPath(absoluteOrRelativePath)
|
||||
? absoluteOrRelativePath
|
||||
: getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /* isAbsolutePathAnUrl */ false);
|
||||
}
|
||||
|
||||
const carriageReturnLineFeed = "\r\n";
|
||||
const lineFeed = "\n";
|
||||
export function getNewLineCharacter(options: CompilerOptions): string {
|
||||
|
||||
+38
-40
@@ -206,7 +206,7 @@ namespace FourSlash {
|
||||
// Whether or not we should format on keystrokes
|
||||
public enableFormatting = true;
|
||||
|
||||
public formatCodeOptions: ts.FormatCodeOptions;
|
||||
public formatCodeSettings: ts.FormatCodeSettings;
|
||||
|
||||
private inputFiles = ts.createMap<string>(); // Map between inputFile's fileName and its content for easily looking up when resolving references
|
||||
|
||||
@@ -350,26 +350,26 @@ namespace FourSlash {
|
||||
Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false);
|
||||
}
|
||||
|
||||
this.formatCodeOptions = {
|
||||
BaseIndentSize: 0,
|
||||
IndentSize: 4,
|
||||
TabSize: 4,
|
||||
NewLineCharacter: Harness.IO.newLine(),
|
||||
ConvertTabsToSpaces: true,
|
||||
IndentStyle: ts.IndentStyle.Smart,
|
||||
InsertSpaceAfterCommaDelimiter: true,
|
||||
InsertSpaceAfterSemicolonInForStatements: true,
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
InsertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
InsertSpaceAfterTypeAssertion: false,
|
||||
PlaceOpenBraceOnNewLineForFunctions: false,
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: false,
|
||||
this.formatCodeSettings = {
|
||||
baseIndentSize: 0,
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
newLineCharacter: Harness.IO.newLine(),
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: ts.IndentStyle.Smart,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
insertSpaceAfterTypeAssertion: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
|
||||
// Open the first file by default
|
||||
@@ -1428,7 +1428,7 @@ namespace FourSlash {
|
||||
|
||||
// Handle post-keystroke formatting
|
||||
if (this.enableFormatting) {
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings);
|
||||
if (edits.length) {
|
||||
offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
// this.checkPostEditInvariants();
|
||||
@@ -1466,7 +1466,7 @@ namespace FourSlash {
|
||||
|
||||
// Handle post-keystroke formatting
|
||||
if (this.enableFormatting) {
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings);
|
||||
if (edits.length) {
|
||||
offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
}
|
||||
@@ -1514,7 +1514,7 @@ namespace FourSlash {
|
||||
|
||||
// Handle post-keystroke formatting
|
||||
if (this.enableFormatting) {
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings);
|
||||
if (edits.length) {
|
||||
offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
}
|
||||
@@ -1538,7 +1538,7 @@ namespace FourSlash {
|
||||
|
||||
// Handle formatting
|
||||
if (this.enableFormatting) {
|
||||
const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeSettings);
|
||||
if (edits.length) {
|
||||
offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
}
|
||||
@@ -1611,30 +1611,30 @@ namespace FourSlash {
|
||||
return runningOffset;
|
||||
}
|
||||
|
||||
public copyFormatOptions(): ts.FormatCodeOptions {
|
||||
return ts.clone(this.formatCodeOptions);
|
||||
public copyFormatOptions(): ts.FormatCodeSettings {
|
||||
return ts.clone(this.formatCodeSettings);
|
||||
}
|
||||
|
||||
public setFormatOptions(formatCodeOptions: ts.FormatCodeOptions): ts.FormatCodeOptions {
|
||||
const oldFormatCodeOptions = this.formatCodeOptions;
|
||||
this.formatCodeOptions = formatCodeOptions;
|
||||
public setFormatOptions(formatCodeOptions: ts.FormatCodeOptions | ts.FormatCodeSettings): ts.FormatCodeSettings {
|
||||
const oldFormatCodeOptions = this.formatCodeSettings;
|
||||
this.formatCodeSettings = ts.toEditorSettings(formatCodeOptions);
|
||||
return oldFormatCodeOptions;
|
||||
}
|
||||
|
||||
public formatDocument() {
|
||||
const edits = this.languageService.getFormattingEditsForDocument(this.activeFile.fileName, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsForDocument(this.activeFile.fileName, this.formatCodeSettings);
|
||||
this.currentCaretPosition += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
this.fixCaretPosition();
|
||||
}
|
||||
|
||||
public formatSelection(start: number, end: number) {
|
||||
const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, end, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, end, this.formatCodeSettings);
|
||||
this.currentCaretPosition += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
this.fixCaretPosition();
|
||||
}
|
||||
|
||||
public formatOnType(pos: number, key: string) {
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, pos, key, this.formatCodeOptions);
|
||||
const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, pos, key, this.formatCodeSettings);
|
||||
this.currentCaretPosition += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true);
|
||||
this.fixCaretPosition();
|
||||
}
|
||||
@@ -1835,11 +1835,9 @@ namespace FourSlash {
|
||||
}
|
||||
|
||||
private getIndentation(fileName: string, position: number, indentStyle: ts.IndentStyle, baseIndentSize: number): number {
|
||||
|
||||
const formatOptions = ts.clone(this.formatCodeOptions);
|
||||
formatOptions.IndentStyle = indentStyle;
|
||||
formatOptions.BaseIndentSize = baseIndentSize;
|
||||
|
||||
const formatOptions = ts.clone(this.formatCodeSettings);
|
||||
formatOptions.indentStyle = indentStyle;
|
||||
formatOptions.baseIndentSize = baseIndentSize;
|
||||
return this.languageService.getIndentationAtPosition(fileName, position, formatOptions);
|
||||
}
|
||||
|
||||
@@ -3485,7 +3483,7 @@ namespace FourSlashInterface {
|
||||
this.state.formatDocument();
|
||||
}
|
||||
|
||||
public copyFormatOptions(): ts.FormatCodeOptions {
|
||||
public copyFormatOptions(): ts.FormatCodeSettings {
|
||||
return this.state.copyFormatOptions();
|
||||
}
|
||||
|
||||
@@ -3505,7 +3503,7 @@ namespace FourSlashInterface {
|
||||
public setOption(name: string, value: string): void;
|
||||
public setOption(name: string, value: boolean): void;
|
||||
public setOption(name: string, value: any): void {
|
||||
this.state.formatCodeOptions[name] = value;
|
||||
(<any>this.state.formatCodeSettings)[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -374,7 +374,7 @@ namespace Utils {
|
||||
// call this on both nodes to ensure all propagated flags have been set (and thus can be
|
||||
// compared).
|
||||
assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
|
||||
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
|
||||
assert.equal(node1.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, node2.flags & ~ts.NodeFlags.ReachabilityAndEmitFlags, "node1.flags !== node2.flags");
|
||||
|
||||
ts.forEachChild(node1,
|
||||
child1 => {
|
||||
|
||||
@@ -218,6 +218,9 @@ namespace Harness.LanguageService {
|
||||
const snapshot = this.getScriptSnapshot(path);
|
||||
return snapshot.getText(0, snapshot.getLength());
|
||||
}
|
||||
getTypeRootsVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
log(s: string): void { }
|
||||
@@ -605,7 +608,6 @@ namespace Harness.LanguageService {
|
||||
this.writeMessage(message);
|
||||
}
|
||||
|
||||
|
||||
readFile(fileName: string): string {
|
||||
if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) {
|
||||
fileName = Harness.Compiler.defaultLibFileName;
|
||||
@@ -681,7 +683,11 @@ namespace Harness.LanguageService {
|
||||
return true;
|
||||
}
|
||||
|
||||
isVerbose() {
|
||||
getLogFileName(): string {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
hasLevel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -703,6 +709,14 @@ namespace Harness.LanguageService {
|
||||
clearTimeout(timeoutId: any): void {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
setImmediate(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
|
||||
return setImmediate(callback, args);
|
||||
}
|
||||
|
||||
clearImmediate(timeoutId: any): void {
|
||||
clearImmediate(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
|
||||
@@ -717,8 +731,12 @@ namespace Harness.LanguageService {
|
||||
// host to answer server queries about files on disk
|
||||
const serverHost = new SessionServerHost(clientHost);
|
||||
const server = new ts.server.Session(serverHost,
|
||||
Buffer ? Buffer.byteLength : (string: string, encoding?: string) => string.length,
|
||||
process.hrtime, serverHost);
|
||||
{ isCancellationRequested: () => false },
|
||||
/*useOneInferredProject*/ false,
|
||||
/*typingsInstaller*/ undefined,
|
||||
Utils.byteLength,
|
||||
process.hrtime, serverHost,
|
||||
/*canUseEvents*/ true);
|
||||
|
||||
// Fake the connection between the client and the server
|
||||
serverHost.writeMessage = client.onMessage.bind(client);
|
||||
|
||||
@@ -105,6 +105,9 @@
|
||||
"./unittests/convertTypingOptionsFromJson.ts",
|
||||
"./unittests/tsserverProjectSystem.ts",
|
||||
"./unittests/matchFiles.ts",
|
||||
"./unittests/initializeTSConfig.ts"
|
||||
"./unittests/initializeTSConfig.ts",
|
||||
"./unittests/compileOnSave.ts",
|
||||
"./unittests/typingsInstaller.ts",
|
||||
"./unittests/projectErrors.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -64,26 +64,29 @@ namespace ts {
|
||||
};
|
||||
},
|
||||
setTimeout,
|
||||
clearTimeout
|
||||
clearTimeout,
|
||||
setImmediate,
|
||||
clearImmediate
|
||||
};
|
||||
}
|
||||
|
||||
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
|
||||
const logger: server.Logger = {
|
||||
close() { },
|
||||
isVerbose: () => false,
|
||||
hasLevel: () => false,
|
||||
loggingEnabled: () => false,
|
||||
perftrc: (s: string) => { },
|
||||
info: (s: string) => { },
|
||||
startGroup: () => { },
|
||||
endGroup: () => { },
|
||||
msg: (s: string, type?: string) => { }
|
||||
msg: (s: string, type?: string) => { },
|
||||
getLogFileName: (): string => undefined
|
||||
};
|
||||
|
||||
const projectService = new server.ProjectService(serverHost, logger);
|
||||
const rootScriptInfo = projectService.openFile(rootFile, /* openedByClient */true);
|
||||
const project = projectService.createInferredProject(rootScriptInfo);
|
||||
project.setProjectOptions({ files: [rootScriptInfo.fileName], compilerOptions: { module: ts.ModuleKind.AMD } });
|
||||
const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false }, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined);
|
||||
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */true, /*containingProject*/ undefined);
|
||||
const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo);
|
||||
project.setCompilerOptions({ module: ts.ModuleKind.AMD } );
|
||||
return {
|
||||
project,
|
||||
rootScriptInfo
|
||||
@@ -106,10 +109,9 @@ namespace ts {
|
||||
const { project, rootScriptInfo } = createProject(root.name, serverHost);
|
||||
|
||||
// ensure that imported file was found
|
||||
let diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
let diags = project.getLanguageService().getSemanticDiagnostics(imported.name);
|
||||
assert.equal(diags.length, 1);
|
||||
|
||||
let content = rootScriptInfo.getText();
|
||||
|
||||
const originalFileExists = serverHost.fileExists;
|
||||
{
|
||||
@@ -121,10 +123,9 @@ namespace ts {
|
||||
|
||||
const newContent = `import {x} from "f1"
|
||||
var x: string = 1;`;
|
||||
rootScriptInfo.editContent(0, content.length, newContent);
|
||||
content = newContent;
|
||||
rootScriptInfo.editContent(0, root.content.length, newContent);
|
||||
// trigger synchronization to make sure that import will be fetched from the cache
|
||||
diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
diags = project.getLanguageService().getSemanticDiagnostics(imported.name);
|
||||
// ensure file has correct number of errors after edit
|
||||
assert.equal(diags.length, 1);
|
||||
}
|
||||
@@ -139,12 +140,11 @@ namespace ts {
|
||||
return originalFileExists.call(serverHost, fileName);
|
||||
};
|
||||
const newContent = `import {x} from "f2"`;
|
||||
rootScriptInfo.editContent(0, content.length, newContent);
|
||||
content = newContent;
|
||||
rootScriptInfo.editContent(0, root.content.length, newContent);
|
||||
|
||||
try {
|
||||
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
|
||||
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
project.getLanguageService().getSemanticDiagnostics(imported.name);
|
||||
assert.isTrue(false, `should not find file '${imported.name}'`);
|
||||
}
|
||||
catch (e) {
|
||||
@@ -165,20 +165,18 @@ namespace ts {
|
||||
};
|
||||
|
||||
const newContent = `import {x} from "f1"`;
|
||||
rootScriptInfo.editContent(0, content.length, newContent);
|
||||
content = newContent;
|
||||
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
rootScriptInfo.editContent(0, root.content.length, newContent);
|
||||
project.getLanguageService().getSemanticDiagnostics(imported.name);
|
||||
assert.isTrue(fileExistsCalled);
|
||||
|
||||
// setting compiler options discards module resolution cache
|
||||
fileExistsCalled = false;
|
||||
|
||||
const opts = ts.clone(project.projectOptions);
|
||||
opts.compilerOptions = ts.clone(opts.compilerOptions);
|
||||
opts.compilerOptions.target = ts.ScriptTarget.ES5;
|
||||
project.setProjectOptions(opts);
|
||||
const compilerOptions = ts.clone(project.getCompilerOptions());
|
||||
compilerOptions.target = ts.ScriptTarget.ES5;
|
||||
project.setCompilerOptions(compilerOptions);
|
||||
|
||||
project.compilerService.languageService.getSemanticDiagnostics(imported.name);
|
||||
project.getLanguageService().getSemanticDiagnostics(imported.name);
|
||||
assert.isTrue(fileExistsCalled);
|
||||
}
|
||||
});
|
||||
@@ -211,8 +209,8 @@ namespace ts {
|
||||
};
|
||||
|
||||
const { project, rootScriptInfo } = createProject(root.name, serverHost);
|
||||
const content = rootScriptInfo.getText();
|
||||
let diags = project.compilerService.languageService.getSemanticDiagnostics(root.name);
|
||||
|
||||
let diags = project.getLanguageService().getSemanticDiagnostics(root.name);
|
||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||
assert.isTrue(diags.length === 1, "one diagnostic expected");
|
||||
assert.isTrue(typeof diags[0].messageText === "string" && ((<string>diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message");
|
||||
@@ -220,9 +218,9 @@ namespace ts {
|
||||
// assert that import will success once file appear on disk
|
||||
fileMap[imported.name] = imported;
|
||||
fileExistsCalledForBar = false;
|
||||
rootScriptInfo.editContent(0, content.length, `import {y} from "bar"`);
|
||||
rootScriptInfo.editContent(0, root.content.length, `import {y} from "bar"`);
|
||||
|
||||
diags = project.compilerService.languageService.getSemanticDiagnostics(root.name);
|
||||
diags = project.getLanguageService().getSemanticDiagnostics(root.name);
|
||||
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
|
||||
assert.isTrue(diags.length === 0);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="./tsserverProjectSystem.ts" />
|
||||
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
|
||||
|
||||
namespace ts.projectSystem {
|
||||
function createTestTypingsInstaller(host: server.ServerHost) {
|
||||
return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
|
||||
}
|
||||
|
||||
describe("CompileOnSave affected list", () => {
|
||||
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) {
|
||||
const response: server.protocol.CompileOnSaveAffectedFileListSingleProject[] = session.executeCommand(request).response;
|
||||
const actualResult = response.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
|
||||
expectedFileList = expectedFileList.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
|
||||
|
||||
assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`);
|
||||
|
||||
for (let i = 0; i < actualResult.length; i++) {
|
||||
const actualResultSingleProject = actualResult[i];
|
||||
const expectedResultSingleProject = expectedFileList[i];
|
||||
assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`);
|
||||
|
||||
const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort();
|
||||
const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort();
|
||||
assert.isTrue(
|
||||
arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList),
|
||||
`For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`);
|
||||
}
|
||||
}
|
||||
|
||||
describe("for configured projects", () => {
|
||||
let moduleFile1: FileOrFolder;
|
||||
let file1Consumer1: FileOrFolder;
|
||||
let file1Consumer2: FileOrFolder;
|
||||
let moduleFile2: FileOrFolder;
|
||||
let globalFile3: FileOrFolder;
|
||||
let configFile: FileOrFolder;
|
||||
let changeModuleFile1ShapeRequest1: server.protocol.Request;
|
||||
let changeModuleFile1InternalRequest1: server.protocol.Request;
|
||||
let changeModuleFile1ShapeRequest2: server.protocol.Request;
|
||||
// A compile on save affected file request using file1
|
||||
let moduleFile1FileListRequest: server.protocol.Request;
|
||||
|
||||
beforeEach(() => {
|
||||
moduleFile1 = {
|
||||
path: "/a/b/moduleFile1.ts",
|
||||
content: "export function Foo() { };"
|
||||
};
|
||||
|
||||
file1Consumer1 = {
|
||||
path: "/a/b/file1Consumer1.ts",
|
||||
content: `import {Foo} from "./moduleFile1"; export var y = 10;`
|
||||
};
|
||||
|
||||
file1Consumer2 = {
|
||||
path: "/a/b/file1Consumer2.ts",
|
||||
content: `import {Foo} from "./moduleFile1"; let z = 10;`
|
||||
};
|
||||
|
||||
moduleFile2 = {
|
||||
path: "/a/b/moduleFile2.ts",
|
||||
content: `export var Foo4 = 10;`
|
||||
};
|
||||
|
||||
globalFile3 = {
|
||||
path: "/a/b/globalFile3.ts",
|
||||
content: `interface GlobalFoo { age: number }`
|
||||
};
|
||||
|
||||
configFile = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{
|
||||
"compileOnSave": true
|
||||
}`
|
||||
};
|
||||
|
||||
// Change the content of file1 to `export var T: number;export function Foo() { };`
|
||||
changeModuleFile1ShapeRequest1 = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 1,
|
||||
insertString: `export var T: number;`
|
||||
});
|
||||
|
||||
// Change the content of file1 to `export var T: number;export function Foo() { };`
|
||||
changeModuleFile1InternalRequest1 = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 1,
|
||||
insertString: `var T1: number;`
|
||||
});
|
||||
|
||||
// Change the content of file1 to `export var T: number;export function Foo() { };`
|
||||
changeModuleFile1ShapeRequest2 = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 1,
|
||||
insertString: `export var T2: number;`
|
||||
});
|
||||
|
||||
moduleFile1FileListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path });
|
||||
});
|
||||
|
||||
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1, file1Consumer1], session);
|
||||
|
||||
// Send an initial compileOnSave request
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
|
||||
// Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };`
|
||||
const changeFile1InternalRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 46,
|
||||
endLine: 1,
|
||||
endOffset: 46,
|
||||
insertString: `console.log('hi');`
|
||||
});
|
||||
session.executeCommand(changeFile1InternalRequest);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
|
||||
});
|
||||
|
||||
it("should be up-to-date with the reference map changes", () => {
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1, file1Consumer1], session);
|
||||
|
||||
// Send an initial compileOnSave request
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
|
||||
// Change file2 content to `let y = Foo();`
|
||||
const removeFile1Consumer1ImportRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: file1Consumer1.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 28,
|
||||
insertString: ""
|
||||
});
|
||||
session.executeCommand(removeFile1Consumer1ImportRequest);
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
|
||||
|
||||
// Add the import statements back to file2
|
||||
const addFile2ImportRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: file1Consumer1.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 1,
|
||||
insertString: `import {Foo} from "./moduleFile1";`
|
||||
});
|
||||
session.executeCommand(addFile2ImportRequest);
|
||||
|
||||
// Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };`
|
||||
const changeModuleFile1ShapeRequest2 = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 1,
|
||||
insertString: `export var T2: string;`
|
||||
});
|
||||
session.executeCommand(changeModuleFile1ShapeRequest2);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
});
|
||||
|
||||
it("should be up-to-date with changes made in non-open files", () => {
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1], session);
|
||||
|
||||
// Send an initial compileOnSave request
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
|
||||
file1Consumer1.content = `let y = 10;`;
|
||||
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
|
||||
host.triggerFileWatcherCallback(file1Consumer1.path, /*removed*/ false);
|
||||
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
|
||||
});
|
||||
|
||||
it("should be up-to-date with deleted files", () => {
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1], session);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
// Delete file1Consumer2
|
||||
host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]);
|
||||
host.triggerFileWatcherCallback(file1Consumer2.path, /*removed*/ true);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
|
||||
});
|
||||
|
||||
it("should be up-to-date with newly created files", () => {
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1], session);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
|
||||
const file1Consumer3: FileOrFolder = {
|
||||
path: "/a/b/file1Consumer3.ts",
|
||||
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
|
||||
};
|
||||
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3, globalFile3, configFile, libFile]);
|
||||
host.triggerDirectoryWatcherCallback(ts.getDirectoryPath(file1Consumer3.path), file1Consumer3.path);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]);
|
||||
});
|
||||
|
||||
it("should detect changes in non-root files", () => {
|
||||
moduleFile1 = {
|
||||
path: "/a/b/moduleFile1.ts",
|
||||
content: "export function Foo() { };"
|
||||
};
|
||||
|
||||
file1Consumer1 = {
|
||||
path: "/a/b/file1Consumer1.ts",
|
||||
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
|
||||
};
|
||||
|
||||
configFile = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{
|
||||
"compileOnSave": true,
|
||||
"files": ["${file1Consumer1.path}"]
|
||||
}`
|
||||
};
|
||||
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1, file1Consumer1], session);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
|
||||
|
||||
// change file1 shape now, and verify both files are affected
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
|
||||
|
||||
// change file1 internal, and verify only file1 is affected
|
||||
session.executeCommand(changeModuleFile1InternalRequest1);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
|
||||
});
|
||||
|
||||
it("should return all files if a global file changed shape", () => {
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([globalFile3], session);
|
||||
const changeGlobalFile3ShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: globalFile3.path,
|
||||
line: 1,
|
||||
offset: 1,
|
||||
endLine: 1,
|
||||
endOffset: 1,
|
||||
insertString: `var T2: string;`
|
||||
});
|
||||
|
||||
// check after file1 shape changes
|
||||
session.executeCommand(changeGlobalFile3ShapeRequest);
|
||||
const globalFile3FileListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path });
|
||||
sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]);
|
||||
});
|
||||
|
||||
it("should return empty array if CompileOnSave is not enabled", () => {
|
||||
configFile = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{}`
|
||||
};
|
||||
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
openFilesForSession([moduleFile1], session);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []);
|
||||
});
|
||||
|
||||
it("should always return the file itself if '--isolatedModules' is specified", () => {
|
||||
configFile = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"isolatedModules": true
|
||||
}
|
||||
}`
|
||||
};
|
||||
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
openFilesForSession([moduleFile1], session);
|
||||
|
||||
const file1ChangeShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 27,
|
||||
endLine: 1,
|
||||
endOffset: 27,
|
||||
insertString: `Point,`
|
||||
});
|
||||
session.executeCommand(file1ChangeShapeRequest);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
|
||||
});
|
||||
|
||||
it("should always return the file itself if '--out' or '--outFile' is specified", () => {
|
||||
configFile = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "system",
|
||||
"outFile": "/a/b/out.js"
|
||||
}
|
||||
}`
|
||||
};
|
||||
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
openFilesForSession([moduleFile1], session);
|
||||
|
||||
const file1ChangeShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: moduleFile1.path,
|
||||
line: 1,
|
||||
offset: 27,
|
||||
endLine: 1,
|
||||
endOffset: 27,
|
||||
insertString: `Point,`
|
||||
});
|
||||
session.executeCommand(file1ChangeShapeRequest);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
|
||||
});
|
||||
|
||||
it("should return cascaded affected file list", () => {
|
||||
const file1Consumer1Consumer1: FileOrFolder = {
|
||||
path: "/a/b/file1Consumer1Consumer1.ts",
|
||||
content: `import {y} from "./file1Consumer1";`
|
||||
};
|
||||
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([moduleFile1, file1Consumer1], session);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
|
||||
|
||||
const changeFile1Consumer1ShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(server.CommandNames.Change, {
|
||||
file: file1Consumer1.path,
|
||||
line: 2,
|
||||
offset: 1,
|
||||
endLine: 2,
|
||||
endOffset: 1,
|
||||
insertString: `export var T: number;`
|
||||
});
|
||||
session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
session.executeCommand(changeFile1Consumer1ShapeRequest);
|
||||
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
|
||||
});
|
||||
|
||||
it("should work fine for files with circular references", () => {
|
||||
const file1: FileOrFolder = {
|
||||
path: "/a/b/file1.ts",
|
||||
content: `
|
||||
/// <reference path="./file2.ts" />
|
||||
export var t1 = 10;`
|
||||
};
|
||||
const file2: FileOrFolder = {
|
||||
path: "/a/b/file2.ts",
|
||||
content: `
|
||||
/// <reference path="./file1.ts" />
|
||||
export var t2 = 10;`
|
||||
};
|
||||
const host = createServerHost([file1, file2, configFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([file1, file2], session);
|
||||
const file1AffectedListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
|
||||
sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]);
|
||||
});
|
||||
|
||||
it("should return results for all projects if not specifying projectFileName", () => {
|
||||
const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" };
|
||||
const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
|
||||
const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
|
||||
const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
|
||||
const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
|
||||
|
||||
const host = createServerHost([file1, file2, file3, configFile1, configFile2]);
|
||||
const session = createSession(host);
|
||||
|
||||
openFilesForSession([file1, file2, file3], session);
|
||||
const file1AffectedListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
|
||||
|
||||
sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [
|
||||
{ projectFileName: configFile1.path, files: [file1, file2] },
|
||||
{ projectFileName: configFile2.path, files: [file1, file3] }
|
||||
]);
|
||||
});
|
||||
|
||||
it("should detect removed code file", () => {
|
||||
const referenceFile1: FileOrFolder = {
|
||||
path: "/a/b/referenceFile1.ts",
|
||||
content: `
|
||||
/// <reference path="./moduleFile1.ts" />
|
||||
export var x = Foo();`
|
||||
};
|
||||
const host = createServerHost([moduleFile1, referenceFile1, configFile]);
|
||||
const session = createSession(host);
|
||||
|
||||
openFilesForSession([referenceFile1], session);
|
||||
host.reloadFS([referenceFile1, configFile]);
|
||||
host.triggerFileWatcherCallback(moduleFile1.path, /*removed*/ true);
|
||||
|
||||
const request = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
|
||||
sendAffectedFileRequestAndCheckResult(session, request, [
|
||||
{ projectFileName: configFile.path, files: [referenceFile1] }
|
||||
]);
|
||||
const requestForMissingFile = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path });
|
||||
sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []);
|
||||
});
|
||||
|
||||
it("should detect non-existing code file", () => {
|
||||
const referenceFile1: FileOrFolder = {
|
||||
path: "/a/b/referenceFile1.ts",
|
||||
content: `
|
||||
/// <reference path="./moduleFile2.ts" />
|
||||
export var x = Foo();`
|
||||
};
|
||||
const host = createServerHost([referenceFile1, configFile]);
|
||||
const session = createSession(host);
|
||||
|
||||
openFilesForSession([referenceFile1], session);
|
||||
const request = makeSessionRequest<server.protocol.FileRequestArgs>(server.CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
|
||||
sendAffectedFileRequestAndCheckResult(session, request, [
|
||||
{ projectFileName: configFile.path, files: [referenceFile1] }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("EmitFile test", () => {
|
||||
it("should emit specified file", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/f1.ts",
|
||||
content: `export function Foo() { return 10; }`
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/f2.ts",
|
||||
content: `import {Foo} from "./f1"; let y = Foo();`
|
||||
};
|
||||
const configFile = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: `{}`
|
||||
};
|
||||
const host = createServerHost([file1, file2, configFile, libFile]);
|
||||
const typingsInstaller = createTestTypingsInstaller(host);
|
||||
const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
|
||||
|
||||
openFilesForSession([file1, file2], session);
|
||||
const compileFileRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(server.CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path });
|
||||
session.executeCommand(compileFileRequest);
|
||||
|
||||
const expectedEmittedFileName = "/a/b/f1.js";
|
||||
assert.isTrue(host.fileExists(expectedEmittedFileName));
|
||||
assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -404,6 +404,7 @@ namespace ts {
|
||||
compilerOptions: <CompilerOptions>{
|
||||
allowJs: true,
|
||||
maxNodeModuleJsDepth: 2,
|
||||
allowSyntheticDefaultImports: true,
|
||||
module: ModuleKind.CommonJS,
|
||||
target: ScriptTarget.ES5,
|
||||
noImplicitAny: false,
|
||||
@@ -431,6 +432,7 @@ namespace ts {
|
||||
compilerOptions: <CompilerOptions>{
|
||||
allowJs: false,
|
||||
maxNodeModuleJsDepth: 2,
|
||||
allowSyntheticDefaultImports: true,
|
||||
module: ModuleKind.CommonJS,
|
||||
target: ScriptTarget.ES5,
|
||||
noImplicitAny: false,
|
||||
@@ -453,7 +455,8 @@ namespace ts {
|
||||
compilerOptions:
|
||||
{
|
||||
allowJs: true,
|
||||
maxNodeModuleJsDepth: 2
|
||||
maxNodeModuleJsDepth: 2,
|
||||
allowSyntheticDefaultImports: true
|
||||
},
|
||||
errors: [{
|
||||
file: undefined,
|
||||
@@ -473,7 +476,8 @@ namespace ts {
|
||||
compilerOptions:
|
||||
{
|
||||
allowJs: true,
|
||||
maxNodeModuleJsDepth: 2
|
||||
maxNodeModuleJsDepth: 2,
|
||||
allowSyntheticDefaultImports: true
|
||||
},
|
||||
errors: <Diagnostic[]>[]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="./tsserverProjectSystem.ts" />
|
||||
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
|
||||
|
||||
namespace ts.projectSystem {
|
||||
describe("Project errors", () => {
|
||||
function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: string[]) {
|
||||
assert.isTrue(projectFiles !== undefined, "missing project files");
|
||||
const errors = projectFiles.projectErrors;
|
||||
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
|
||||
if (expectedErrors.length) {
|
||||
for (let i = 0; i < errors.length; i++) {
|
||||
const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n");
|
||||
const expectedMessage = expectedErrors[i];
|
||||
assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it("external project - diagnostics for missing files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/lib.ts",
|
||||
content: ""
|
||||
};
|
||||
// only file1 exists - expect error
|
||||
const host = createServerHost([file1]);
|
||||
const projectService = createProjectService(host);
|
||||
const projectFileName = "/a/b/test.csproj";
|
||||
|
||||
{
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: toExternalFiles([file1.path, file2.path])
|
||||
});
|
||||
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
const knownProjects = projectService.synchronizeProjectList([]);
|
||||
checkProjectErrors(knownProjects[0], ["File '/a/b/lib.ts' not found."]);
|
||||
}
|
||||
// only file2 exists - expect error
|
||||
host.reloadFS([file2]);
|
||||
{
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: toExternalFiles([file1.path, file2.path])
|
||||
});
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
const knownProjects = projectService.synchronizeProjectList([]);
|
||||
checkProjectErrors(knownProjects[0], ["File '/a/b/app.ts' not found."]);
|
||||
}
|
||||
|
||||
// both files exist - expect no errors
|
||||
host.reloadFS([file1, file2]);
|
||||
{
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: toExternalFiles([file1.path, file2.path])
|
||||
});
|
||||
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
const knownProjects = projectService.synchronizeProjectList([]);
|
||||
checkProjectErrors(knownProjects[0], []);
|
||||
}
|
||||
});
|
||||
|
||||
it("configured projects - diagnostics for missing files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/lib.ts",
|
||||
content: ""
|
||||
};
|
||||
const config = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
|
||||
};
|
||||
const host = createServerHost([file1, config]);
|
||||
const projectService = createProjectService(host);
|
||||
|
||||
projectService.openClientFile(file1.path);
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]);
|
||||
|
||||
host.reloadFS([file1, file2, config]);
|
||||
|
||||
projectService.openClientFile(file1.path);
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
checkProjectErrors(projectService.synchronizeProjectList([])[0], []);
|
||||
});
|
||||
|
||||
it("configured projects - diagnostics for corrupted config 1", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/lib.ts",
|
||||
content: ""
|
||||
};
|
||||
const correctConfig = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
|
||||
};
|
||||
const corruptedConfig = {
|
||||
path: correctConfig.path,
|
||||
content: correctConfig.content.substr(1)
|
||||
};
|
||||
const host = createServerHost([file1, file2, corruptedConfig]);
|
||||
const projectService = createProjectService(host);
|
||||
|
||||
projectService.openClientFile(file1.path);
|
||||
{
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
|
||||
assert.isTrue(configuredProject !== undefined, "should find configured project");
|
||||
checkProjectErrors(configuredProject, [
|
||||
"')' expected.",
|
||||
"Declaration or statement expected.",
|
||||
"Declaration or statement expected.",
|
||||
"Failed to parse file '/a/b/tsconfig.json'"
|
||||
]);
|
||||
}
|
||||
// fix config and trigger watcher
|
||||
host.reloadFS([file1, file2, correctConfig]);
|
||||
host.triggerFileWatcherCallback(correctConfig.path, /*false*/);
|
||||
{
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
|
||||
assert.isTrue(configuredProject !== undefined, "should find configured project");
|
||||
checkProjectErrors(configuredProject, []);
|
||||
}
|
||||
});
|
||||
|
||||
it("configured projects - diagnostics for corrupted config 2", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/lib.ts",
|
||||
content: ""
|
||||
};
|
||||
const correctConfig = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
|
||||
};
|
||||
const corruptedConfig = {
|
||||
path: correctConfig.path,
|
||||
content: correctConfig.content.substr(1)
|
||||
};
|
||||
const host = createServerHost([file1, file2, correctConfig]);
|
||||
const projectService = createProjectService(host);
|
||||
|
||||
projectService.openClientFile(file1.path);
|
||||
{
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
|
||||
assert.isTrue(configuredProject !== undefined, "should find configured project");
|
||||
checkProjectErrors(configuredProject, []);
|
||||
}
|
||||
// break config and trigger watcher
|
||||
host.reloadFS([file1, file2, corruptedConfig]);
|
||||
host.triggerFileWatcherCallback(corruptedConfig.path, /*false*/);
|
||||
{
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
|
||||
assert.isTrue(configuredProject !== undefined, "should find configured project");
|
||||
checkProjectErrors(configuredProject, [
|
||||
"')' expected.",
|
||||
"Declaration or statement expected.",
|
||||
"Declaration or statement expected.",
|
||||
"Failed to parse file '/a/b/tsconfig.json'"
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -22,17 +22,21 @@ namespace ts.server {
|
||||
readDirectory(): string[] { return []; },
|
||||
exit(): void { },
|
||||
setTimeout(callback, ms, ...args) { return 0; },
|
||||
clearTimeout(timeoutId) { }
|
||||
clearTimeout(timeoutId) { },
|
||||
setImmediate: () => 0,
|
||||
clearImmediate() {}
|
||||
};
|
||||
const nullCancellationToken: HostCancellationToken = { isCancellationRequested: () => false };
|
||||
const mockLogger: Logger = {
|
||||
close(): void {},
|
||||
isVerbose(): boolean { return false; },
|
||||
hasLevel(): boolean { return false; },
|
||||
loggingEnabled(): boolean { return false; },
|
||||
perftrc(s: string): void {},
|
||||
info(s: string): void {},
|
||||
startGroup(): void {},
|
||||
endGroup(): void {},
|
||||
msg(s: string, type?: string): void {},
|
||||
getLogFileName: (): string => undefined
|
||||
};
|
||||
|
||||
describe("the Session class", () => {
|
||||
@@ -40,7 +44,7 @@ namespace ts.server {
|
||||
let lastSent: protocol.Message;
|
||||
|
||||
beforeEach(() => {
|
||||
session = new Session(mockHost, Utils.byteLength, process.hrtime, mockLogger);
|
||||
session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
|
||||
session.send = (msg: protocol.Message) => {
|
||||
lastSent = msg;
|
||||
};
|
||||
@@ -265,7 +269,7 @@ namespace ts.server {
|
||||
lastSent: protocol.Message;
|
||||
customHandler = "testhandler";
|
||||
constructor() {
|
||||
super(mockHost, Utils.byteLength, process.hrtime, mockLogger);
|
||||
super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
|
||||
this.addProtocolHandler(this.customHandler, () => {
|
||||
return { response: undefined, responseRequired: true };
|
||||
});
|
||||
@@ -323,7 +327,7 @@ namespace ts.server {
|
||||
class InProcSession extends Session {
|
||||
private queue: protocol.Request[] = [];
|
||||
constructor(private client: InProcClient) {
|
||||
super(mockHost, Utils.byteLength, process.hrtime, mockLogger);
|
||||
super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true);
|
||||
this.addProtocolHandler("echo", (req: protocol.Request) => ({
|
||||
response: req.arguments,
|
||||
responseRequired: true
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace ts {
|
||||
}
|
||||
"files": ["file1.ts"]
|
||||
}`;
|
||||
const { configJsonObject, diagnostics } = parseAndReEmitConfigJSONFile(content);
|
||||
const { configJsonObject, diagnostics } = sanitizeConfigFile("config.json", content);
|
||||
const expectedResult = {
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,780 @@
|
||||
/// <reference path="../harness.ts" />
|
||||
/// <reference path="./tsserverProjectSystem.ts" />
|
||||
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
|
||||
|
||||
namespace ts.projectSystem {
|
||||
import TI = server.typingsInstaller;
|
||||
|
||||
interface InstallerParams {
|
||||
globalTypingsCacheLocation?: string;
|
||||
throttleLimit?: number;
|
||||
}
|
||||
|
||||
class Installer extends TestTypingsInstaller {
|
||||
constructor(host: server.ServerHost, p?: InstallerParams, log?: TI.Log) {
|
||||
super(
|
||||
(p && p.globalTypingsCacheLocation) || "/a/data",
|
||||
(p && p.throttleLimit) || 5,
|
||||
host,
|
||||
log);
|
||||
}
|
||||
|
||||
installAll(expectedView: typeof TI.NpmViewRequest[], expectedInstall: typeof TI.NpmInstallRequest[]) {
|
||||
this.checkPendingCommands(expectedView);
|
||||
this.executePendingCommands();
|
||||
this.checkPendingCommands(expectedInstall);
|
||||
this.executePendingCommands();
|
||||
}
|
||||
}
|
||||
|
||||
describe("typingsInstaller", () => {
|
||||
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], requestKind: TI.RequestKind, cb: TI.RequestCompletedAction): void {
|
||||
switch (requestKind) {
|
||||
case TI.NpmInstallRequest:
|
||||
self.addPostExecAction(requestKind, installedTypings, (err, stdout, stderr) => {
|
||||
for (const file of typingFiles) {
|
||||
host.createFileOrFolder(file, /*createParentDirectory*/ true);
|
||||
}
|
||||
cb(err, stdout, stderr);
|
||||
});
|
||||
break;
|
||||
case TI.NpmViewRequest:
|
||||
self.addPostExecAction(requestKind, installedTypings, cb);
|
||||
break;
|
||||
default:
|
||||
assert.isTrue(false, `unexpected request kind ${requestKind}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
it("configured projects (typings installed) 1", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.js",
|
||||
content: ""
|
||||
};
|
||||
const tsconfig = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
allowJs: true
|
||||
},
|
||||
typingOptions: {
|
||||
enableAutoDiscovery: true
|
||||
}
|
||||
})
|
||||
};
|
||||
const packageJson = {
|
||||
path: "/a/b/package.json",
|
||||
content: JSON.stringify({
|
||||
name: "test",
|
||||
dependencies: {
|
||||
jquery: "^3.1.0"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const jquery = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const $: { x: number }"
|
||||
};
|
||||
const host = createServerHost([file1, tsconfig, packageJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/jquery"];
|
||||
const typingFiles = [jquery];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
|
||||
projectService.openClientFile(file1.path);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = projectService.configuredProjects[0];
|
||||
checkProjectActualFiles(p, [file1.path]);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, jquery.path]);
|
||||
});
|
||||
|
||||
it("inferred project (typings installed)", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.js",
|
||||
content: ""
|
||||
};
|
||||
const packageJson = {
|
||||
path: "/a/b/package.json",
|
||||
content: JSON.stringify({
|
||||
name: "test",
|
||||
dependencies: {
|
||||
jquery: "^3.1.0"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const jquery = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const $: { x: number }"
|
||||
};
|
||||
const host = createServerHost([file1, packageJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/jquery"];
|
||||
const typingFiles = [jquery];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
|
||||
projectService.openClientFile(file1.path);
|
||||
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
const p = projectService.inferredProjects[0];
|
||||
checkProjectActualFiles(p, [file1.path]);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, jquery.path]);
|
||||
});
|
||||
|
||||
it("external project - no typing options, no .d.ts/js files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([file1]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
enqueueInstallTypingsRequest() {
|
||||
assert(false, "auto discovery should not be enabled");
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: [toExternalFile(file1.path)]
|
||||
});
|
||||
installer.checkPendingCommands([]);
|
||||
// by default auto discovery will kick in if project contain only .js/.d.ts files
|
||||
// in this case project contain only ts files - no auto discovery
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
});
|
||||
|
||||
it("external project - no autoDiscovery in typing options, no .d.ts/js files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([file1]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
enqueueInstallTypingsRequest() {
|
||||
assert(false, "auto discovery should not be enabled");
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: [toExternalFile(file1.path)],
|
||||
typingOptions: { include: ["jquery"] }
|
||||
});
|
||||
installer.checkPendingCommands([]);
|
||||
// by default auto discovery will kick in if project contain only .js/.d.ts files
|
||||
// in this case project contain only ts files - no auto discovery even if typing options is set
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
});
|
||||
|
||||
it("external project - autoDiscovery = true, no .d.ts/js files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: ""
|
||||
};
|
||||
const jquery = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const $: { x: number }"
|
||||
};
|
||||
const host = createServerHost([file1]);
|
||||
let enqueueIsCalled = false;
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
|
||||
enqueueIsCalled = true;
|
||||
super.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings = ["@types/jquery"];
|
||||
const typingFiles = [jquery];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: [toExternalFile(file1.path)],
|
||||
typingOptions: { enableAutoDiscovery: true, include: ["node"] }
|
||||
});
|
||||
|
||||
assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true");
|
||||
installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
|
||||
// autoDiscovery is set in typing options - use it even if project contains only .ts files
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
});
|
||||
|
||||
it("external project - no typing options, with only js, jsx, d.ts files", () => {
|
||||
// Tests:
|
||||
// 1. react typings are installed for .jsx
|
||||
// 2. loose files names are matched against safe list for typings if
|
||||
// this is a JS project (only js, jsx, d.ts files are present)
|
||||
const file1 = {
|
||||
path: "/a/b/lodash.js",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/file2.jsx",
|
||||
content: ""
|
||||
};
|
||||
const file3 = {
|
||||
path: "/a/b/file3.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const react = {
|
||||
path: "/a/data/node_modules/@types/react/index.d.ts",
|
||||
content: "declare const react: { x: number }"
|
||||
};
|
||||
const lodash = {
|
||||
path: "/a/data/node_modules/@types/lodash/index.d.ts",
|
||||
content: "declare const lodash: { x: number }"
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, file2, file3]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings = ["@types/lodash", "@types/react"];
|
||||
const typingFiles = [lodash, react];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
|
||||
rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path), toExternalFile(file3.path)],
|
||||
typingOptions: {}
|
||||
});
|
||||
|
||||
const p = projectService.externalProjects[0];
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path, file3.path]);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest, TI.NpmViewRequest], [TI.NpmInstallRequest], );
|
||||
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]);
|
||||
});
|
||||
|
||||
it("external project - no typing options, with js & ts files", () => {
|
||||
// Tests:
|
||||
// 1. No typings are included for JS projects when the project contains ts files
|
||||
const file1 = {
|
||||
path: "/a/b/jquery.js",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/file2.ts",
|
||||
content: ""
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, file2]);
|
||||
let enqueueIsCalled = false;
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
|
||||
enqueueIsCalled = true;
|
||||
super.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings: string[] = [];
|
||||
const typingFiles: FileOrFolder[] = [];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
|
||||
rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path)],
|
||||
typingOptions: {}
|
||||
});
|
||||
|
||||
const p = projectService.externalProjects[0];
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path]);
|
||||
|
||||
installer.checkPendingCommands([]);
|
||||
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path]);
|
||||
});
|
||||
|
||||
it("external project - with typing options, with only js, d.ts files", () => {
|
||||
// Tests:
|
||||
// 1. Safelist matching, typing options includes/excludes and package.json typings are all acquired
|
||||
// 2. Types for safelist matches are not included when they also appear in the typing option exclude list
|
||||
// 3. Multiple includes and excludes are respected in typing options
|
||||
const file1 = {
|
||||
path: "/a/b/lodash.js",
|
||||
content: ""
|
||||
};
|
||||
const file2 = {
|
||||
path: "/a/b/commander.js",
|
||||
content: ""
|
||||
};
|
||||
const file3 = {
|
||||
path: "/a/b/file3.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const packageJson = {
|
||||
path: "/a/b/package.json",
|
||||
content: JSON.stringify({
|
||||
name: "test",
|
||||
dependencies: {
|
||||
express: "^3.1.0"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const commander = {
|
||||
path: "/a/data/node_modules/@types/commander/index.d.ts",
|
||||
content: "declare const commander: { x: number }"
|
||||
};
|
||||
const express = {
|
||||
path: "/a/data/node_modules/@types/express/index.d.ts",
|
||||
content: "declare const express: { x: number }"
|
||||
};
|
||||
const jquery = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const jquery: { x: number }"
|
||||
};
|
||||
const moment = {
|
||||
path: "/a/data/node_modules/@types/moment/index.d.ts",
|
||||
content: "declare const moment: { x: number }"
|
||||
};
|
||||
|
||||
const host = createServerHost([file1, file2, file3, packageJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment"];
|
||||
const typingFiles = [commander, express, jquery, moment];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
|
||||
rootFiles: [toExternalFile(file1.path), toExternalFile(file2.path), toExternalFile(file3.path)],
|
||||
typingOptions: { include: ["jquery", "moment"], exclude: ["lodash"] }
|
||||
});
|
||||
|
||||
const p = projectService.externalProjects[0];
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path, file3.path]);
|
||||
|
||||
installer.installAll(
|
||||
[TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest],
|
||||
[TI.NpmInstallRequest]
|
||||
);
|
||||
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]);
|
||||
});
|
||||
|
||||
it("Throttle - delayed typings to install", () => {
|
||||
const lodashJs = {
|
||||
path: "/a/b/lodash.js",
|
||||
content: ""
|
||||
};
|
||||
const commanderJs = {
|
||||
path: "/a/b/commander.js",
|
||||
content: ""
|
||||
};
|
||||
const file3 = {
|
||||
path: "/a/b/file3.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const packageJson = {
|
||||
path: "/a/b/package.json",
|
||||
content: JSON.stringify({
|
||||
name: "test",
|
||||
dependencies: {
|
||||
express: "^3.1.0"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const commander = {
|
||||
path: "/a/data/node_modules/@types/commander/index.d.ts",
|
||||
content: "declare const commander: { x: number }"
|
||||
};
|
||||
const express = {
|
||||
path: "/a/data/node_modules/@types/express/index.d.ts",
|
||||
content: "declare const express: { x: number }"
|
||||
};
|
||||
const jquery = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const jquery: { x: number }"
|
||||
};
|
||||
const moment = {
|
||||
path: "/a/data/node_modules/@types/moment/index.d.ts",
|
||||
content: "declare const moment: { x: number }"
|
||||
};
|
||||
const lodash = {
|
||||
path: "/a/data/node_modules/@types/lodash/index.d.ts",
|
||||
content: "declare const lodash: { x: number }"
|
||||
};
|
||||
|
||||
const typingFiles = [commander, express, jquery, moment, lodash];
|
||||
const host = createServerHost([lodashJs, commanderJs, file3, packageJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { throttleLimit: 3 });
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment", "@types/lodash"];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typingOptions: { include: ["jquery", "moment"] }
|
||||
});
|
||||
|
||||
const p = projectService.externalProjects[0];
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path]);
|
||||
// expected 3 view requests in the queue
|
||||
installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest]);
|
||||
assert.equal(installer.pendingRunRequests.length, 2, "expected 2 pending requests");
|
||||
|
||||
// push view requests
|
||||
installer.executePendingCommands();
|
||||
// expected 2 remaining view requests in the queue
|
||||
installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest]);
|
||||
// push view requests
|
||||
installer.executePendingCommands();
|
||||
// expected one install request
|
||||
installer.checkPendingCommands([TI.NpmInstallRequest]);
|
||||
installer.executePendingCommands();
|
||||
// expected all typings file to exist
|
||||
for (const f of typingFiles) {
|
||||
assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`);
|
||||
}
|
||||
|
||||
checkNumberOfProjects(projectService, { externalProjects: 1 });
|
||||
checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]);
|
||||
});
|
||||
|
||||
it("Throttle - delayed run install requests", () => {
|
||||
const lodashJs = {
|
||||
path: "/a/b/lodash.js",
|
||||
content: ""
|
||||
};
|
||||
const commanderJs = {
|
||||
path: "/a/b/commander.js",
|
||||
content: ""
|
||||
};
|
||||
const file3 = {
|
||||
path: "/a/b/file3.d.ts",
|
||||
content: ""
|
||||
};
|
||||
|
||||
const commander = {
|
||||
path: "/a/data/node_modules/@types/commander/index.d.ts",
|
||||
content: "declare const commander: { x: number }",
|
||||
typings: "@types/commander"
|
||||
};
|
||||
const jquery = {
|
||||
path: "/a/data/node_modules/@types/jquery/index.d.ts",
|
||||
content: "declare const jquery: { x: number }",
|
||||
typings: "@types/jquery"
|
||||
};
|
||||
const lodash = {
|
||||
path: "/a/data/node_modules/@types/lodash/index.d.ts",
|
||||
content: "declare const lodash: { x: number }",
|
||||
typings: "@types/lodash"
|
||||
};
|
||||
const cordova = {
|
||||
path: "/a/data/node_modules/@types/cordova/index.d.ts",
|
||||
content: "declare const cordova: { x: number }",
|
||||
typings: "@types/cordova"
|
||||
};
|
||||
const grunt = {
|
||||
path: "/a/data/node_modules/@types/grunt/index.d.ts",
|
||||
content: "declare const grunt: { x: number }",
|
||||
typings: "@types/grunt"
|
||||
};
|
||||
const gulp = {
|
||||
path: "/a/data/node_modules/@types/gulp/index.d.ts",
|
||||
content: "declare const gulp: { x: number }",
|
||||
typings: "@types/gulp"
|
||||
};
|
||||
|
||||
const host = createServerHost([lodashJs, commanderJs, file3]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { throttleLimit: 3 });
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
if (requestKind === TI.NpmInstallRequest) {
|
||||
let typingFiles: (FileOrFolder & { typings: string}) [] = [];
|
||||
if (command.indexOf("commander") >= 0) {
|
||||
typingFiles = [commander, jquery, lodash, cordova];
|
||||
}
|
||||
else {
|
||||
typingFiles = [grunt, gulp];
|
||||
}
|
||||
executeCommand(this, host, typingFiles.map(f => f.typings), typingFiles, requestKind, cb);
|
||||
}
|
||||
else {
|
||||
executeCommand(this, host, [], [], requestKind, cb);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Create project #1 with 4 typings
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
const projectFileName1 = "/a/app/test1.csproj";
|
||||
projectService.openExternalProject({
|
||||
projectFileName: projectFileName1,
|
||||
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
|
||||
rootFiles: [toExternalFile(lodashJs.path), toExternalFile(commanderJs.path), toExternalFile(file3.path)],
|
||||
typingOptions: { include: ["jquery", "cordova" ] }
|
||||
});
|
||||
|
||||
installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest]);
|
||||
assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request");
|
||||
|
||||
// Create project #2 with 2 typings
|
||||
const projectFileName2 = "/a/app/test2.csproj";
|
||||
projectService.openExternalProject({
|
||||
projectFileName: projectFileName2,
|
||||
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
|
||||
rootFiles: [toExternalFile(file3.path)],
|
||||
typingOptions: { include: ["grunt", "gulp"] }
|
||||
});
|
||||
assert.equal(installer.pendingRunRequests.length, 3, "expect three throttled request");
|
||||
|
||||
const p1 = projectService.externalProjects[0];
|
||||
const p2 = projectService.externalProjects[1];
|
||||
projectService.checkNumberOfProjects({ externalProjects: 2 });
|
||||
checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path]);
|
||||
checkProjectActualFiles(p2, [file3.path]);
|
||||
|
||||
|
||||
installer.executePendingCommands();
|
||||
// expected one view request from the first project and two - from the second one
|
||||
installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest]);
|
||||
assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests");
|
||||
|
||||
installer.executePendingCommands();
|
||||
|
||||
// should be two install requests from both projects
|
||||
installer.checkPendingCommands([TI.NpmInstallRequest, TI.NpmInstallRequest]);
|
||||
installer.executePendingCommands();
|
||||
|
||||
checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]);
|
||||
checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path ]);
|
||||
});
|
||||
|
||||
it("configured projects discover from node_modules", () => {
|
||||
const app = {
|
||||
path: "/app.js",
|
||||
content: ""
|
||||
};
|
||||
const jsconfig = {
|
||||
path: "/jsconfig.json",
|
||||
content: JSON.stringify({})
|
||||
};
|
||||
const jquery = {
|
||||
path: "/node_modules/jquery/index.js",
|
||||
content: ""
|
||||
};
|
||||
const jqueryPackage = {
|
||||
path: "/node_modules/jquery/package.json",
|
||||
content: JSON.stringify({ name: "jquery" })
|
||||
};
|
||||
const jqueryDTS = {
|
||||
path: "/tmp/node_modules/@types/jquery/index.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([app, jsconfig, jquery, jqueryPackage]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: "/tmp" });
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/jquery"];
|
||||
const typingFiles = [jqueryDTS];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
|
||||
projectService.openClientFile(app.path);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = projectService.configuredProjects[0];
|
||||
checkProjectActualFiles(p, [app.path]);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
checkProjectActualFiles(p, [app.path, jqueryDTS.path]);
|
||||
});
|
||||
|
||||
it("configured projects discover from bower.josn", () => {
|
||||
const app = {
|
||||
path: "/app.js",
|
||||
content: ""
|
||||
};
|
||||
const jsconfig = {
|
||||
path: "/jsconfig.json",
|
||||
content: JSON.stringify({})
|
||||
};
|
||||
const bowerJson = {
|
||||
path: "/bower.json",
|
||||
content: JSON.stringify({
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
})
|
||||
};
|
||||
const jqueryDTS = {
|
||||
path: "/tmp/node_modules/@types/jquery/index.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([app, jsconfig, bowerJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: "/tmp" });
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/jquery"];
|
||||
const typingFiles = [jqueryDTS];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true, typingsInstaller: installer });
|
||||
projectService.openClientFile(app.path);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
const p = projectService.configuredProjects[0];
|
||||
checkProjectActualFiles(p, [app.path]);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
checkProjectActualFiles(p, [app.path, jqueryDTS.path]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Validate package name:", () => {
|
||||
it ("name cannot be too long", () => {
|
||||
let packageName = "a";
|
||||
for (let i = 0; i < 8; i++) {
|
||||
packageName += packageName;
|
||||
}
|
||||
assert.equal(TI.validatePackageName(packageName), TI.PackageNameValidationResult.NameTooLong);
|
||||
});
|
||||
it ("name cannot start with dot", () => {
|
||||
assert.equal(TI.validatePackageName(".foo"), TI.PackageNameValidationResult.NameStartsWithDot);
|
||||
});
|
||||
it ("name cannot start with underscore", () => {
|
||||
assert.equal(TI.validatePackageName("_foo"), TI.PackageNameValidationResult.NameStartsWithUnderscore);
|
||||
});
|
||||
it ("scoped packages not supported", () => {
|
||||
assert.equal(TI.validatePackageName("@scope/bar"), TI.PackageNameValidationResult.ScopedPackagesNotSupported);
|
||||
});
|
||||
it ("non URI safe characters are not supported", () => {
|
||||
assert.equal(TI.validatePackageName(" scope "), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(TI.validatePackageName("; say ‘Hello from TypeScript!’ #"), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
assert.equal(TI.validatePackageName("a/b/c"), TI.PackageNameValidationResult.NameContainsNonURISafeCharacters);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Invalid package names", () => {
|
||||
it ("should not be installed", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.js",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const packageJson = {
|
||||
path: "/a/b/package.json",
|
||||
content: JSON.stringify({
|
||||
"dependencies": {
|
||||
"; say ‘Hello from TypeScript!’ #": "0.0.x"
|
||||
}
|
||||
})
|
||||
};
|
||||
const messages: string[] = [];
|
||||
const host = createServerHost([f1, packageJson]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
|
||||
}
|
||||
runCommand(requestKind: TI.RequestKind, requestId: number, command: string, cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
assert(false, "runCommand should not be invoked");
|
||||
}
|
||||
})();
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openClientFile(f1.path);
|
||||
|
||||
installer.checkPendingCommands([]);
|
||||
assert.isTrue(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
/// <reference path="..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="protocol.d.ts" />
|
||||
/// <reference path="session.ts" />
|
||||
/// <reference types="node" />
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
interface Hash {
|
||||
update(data: any, input_encoding?: string): Hash;
|
||||
digest(encoding: string): any;
|
||||
}
|
||||
|
||||
const crypto: {
|
||||
createHash(algorithm: string): Hash
|
||||
} = require("crypto");
|
||||
|
||||
export function shouldEmitFile(scriptInfo: ScriptInfo) {
|
||||
return !scriptInfo.hasMixedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract file info that maintains a shape signature.
|
||||
*/
|
||||
export class BuilderFileInfo {
|
||||
|
||||
private lastCheckedShapeSignature: string;
|
||||
|
||||
constructor(public readonly scriptInfo: ScriptInfo, public readonly project: Project) {
|
||||
}
|
||||
|
||||
public isExternalModuleOrHasOnlyAmbientExternalModules() {
|
||||
const sourceFile = this.getSourceFile();
|
||||
return isExternalModule(sourceFile) || this.containsOnlyAmbientModules(sourceFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* For script files that contains only ambient external modules, although they are not actually external module files,
|
||||
* they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
|
||||
* there are no point to rebuild all script files if these special files have changed. However, if any statement
|
||||
* in the file is not ambient external module, we treat it as a regular script file.
|
||||
*/
|
||||
private containsOnlyAmbientModules(sourceFile: SourceFile) {
|
||||
for (const statement of sourceFile.statements) {
|
||||
if (statement.kind !== SyntaxKind.ModuleDeclaration || (<ModuleDeclaration>statement).name.kind !== SyntaxKind.StringLiteral) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private computeHash(text: string): string {
|
||||
return crypto.createHash("md5")
|
||||
.update(text)
|
||||
.digest("base64");
|
||||
}
|
||||
|
||||
private getSourceFile(): SourceFile {
|
||||
return this.project.getSourceFile(this.scriptInfo.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} indicates if the shape signature has changed since last update.
|
||||
*/
|
||||
public updateShapeSignature() {
|
||||
const sourceFile = this.getSourceFile();
|
||||
if (!sourceFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastSignature = this.lastCheckedShapeSignature;
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
this.lastCheckedShapeSignature = this.computeHash(sourceFile.text);
|
||||
}
|
||||
else {
|
||||
const emitOutput = this.project.getFileEmitOutput(this.scriptInfo, /*emitOnlyDtsFiles*/ true);
|
||||
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
||||
this.lastCheckedShapeSignature = this.computeHash(emitOutput.outputFiles[0].text);
|
||||
}
|
||||
}
|
||||
return !lastSignature || this.lastCheckedShapeSignature !== lastSignature;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Builder {
|
||||
readonly project: Project;
|
||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
||||
onProjectUpdateGraph(): void;
|
||||
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean;
|
||||
}
|
||||
|
||||
abstract class AbstractBuilder<T extends BuilderFileInfo> implements Builder {
|
||||
|
||||
private fileInfos = createFileMap<T>();
|
||||
|
||||
constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) {
|
||||
}
|
||||
|
||||
protected getFileInfo(path: Path): T {
|
||||
return this.fileInfos.get(path);
|
||||
}
|
||||
|
||||
protected getOrCreateFileInfo(path: Path): T {
|
||||
let fileInfo = this.getFileInfo(path);
|
||||
if (!fileInfo) {
|
||||
const scriptInfo = this.project.getScriptInfo(path);
|
||||
fileInfo = new this.ctor(scriptInfo, this.project);
|
||||
this.setFileInfo(path, fileInfo);
|
||||
}
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
protected getFileInfoPaths(): Path[] {
|
||||
return this.fileInfos.getKeys();
|
||||
}
|
||||
|
||||
protected setFileInfo(path: Path, info: T) {
|
||||
this.fileInfos.set(path, info);
|
||||
}
|
||||
|
||||
protected removeFileInfo(path: Path) {
|
||||
this.fileInfos.remove(path);
|
||||
}
|
||||
|
||||
protected forEachFileInfo(action: (fileInfo: T) => any) {
|
||||
this.fileInfos.forEachValue((path: Path, value: T) => action(value));
|
||||
}
|
||||
|
||||
abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
|
||||
abstract onProjectUpdateGraph(): void;
|
||||
|
||||
/**
|
||||
* @returns {boolean} whether the emit was conducted or not
|
||||
*/
|
||||
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
|
||||
const fileInfo = this.getFileInfo(scriptInfo.path);
|
||||
if (!fileInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false);
|
||||
if (!emitSkipped) {
|
||||
const projectRootPath = this.project.getProjectRootPath();
|
||||
for (const outputFile of outputFiles) {
|
||||
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName));
|
||||
writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
|
||||
}
|
||||
}
|
||||
return !emitSkipped;
|
||||
}
|
||||
}
|
||||
|
||||
class NonModuleBuilder extends AbstractBuilder<BuilderFileInfo> {
|
||||
|
||||
constructor(public readonly project: Project) {
|
||||
super(project, BuilderFileInfo);
|
||||
}
|
||||
|
||||
onProjectUpdateGraph() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: didn't use path as parameter because the returned file names will be directly
|
||||
* consumed by the API user, which will use it to interact with file systems. Path
|
||||
* should only be used internally, because the case sensitivity is not trustable.
|
||||
*/
|
||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[] {
|
||||
const info = this.getOrCreateFileInfo(scriptInfo.path);
|
||||
const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName];
|
||||
if (info.updateShapeSignature()) {
|
||||
const options = this.project.getCompilerOptions();
|
||||
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
|
||||
// so returning the file itself is good enough.
|
||||
if (options && (options.out || options.outFile)) {
|
||||
return singleFileResult;
|
||||
}
|
||||
return this.project.getAllEmittableFiles();
|
||||
}
|
||||
return singleFileResult;
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleBuilderFileInfo extends BuilderFileInfo {
|
||||
references: ModuleBuilderFileInfo[] = [];
|
||||
referencedBy: ModuleBuilderFileInfo[] = [];
|
||||
scriptVersionForReferences: string;
|
||||
|
||||
static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): number {
|
||||
const l = lf.scriptInfo.fileName;
|
||||
const r = rf.scriptInfo.fileName;
|
||||
return (l < r ? -1 : (l > r ? 1 : 0));
|
||||
};
|
||||
|
||||
static addToReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) {
|
||||
if (array.length === 0) {
|
||||
array.push(fileInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
const insertIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
||||
if (insertIndex < 0) {
|
||||
array.splice(~insertIndex, 0, fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static removeFromReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) {
|
||||
if (!array || array.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (array[0] === fileInfo) {
|
||||
array.splice(0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const removeIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
|
||||
if (removeIndex >= 0) {
|
||||
array.splice(removeIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
addReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
|
||||
ModuleBuilderFileInfo.addToReferenceList(this.referencedBy, fileInfo);
|
||||
}
|
||||
|
||||
removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
|
||||
ModuleBuilderFileInfo.removeFromReferenceList(this.referencedBy, fileInfo);
|
||||
}
|
||||
|
||||
removeFileReferences() {
|
||||
for (const reference of this.references) {
|
||||
reference.removeReferencedBy(this);
|
||||
}
|
||||
this.references = [];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleBuilder extends AbstractBuilder<ModuleBuilderFileInfo> {
|
||||
|
||||
constructor(public readonly project: Project) {
|
||||
super(project, ModuleBuilderFileInfo);
|
||||
}
|
||||
|
||||
private projectVersionForDependencyGraph: string;
|
||||
|
||||
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] {
|
||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path);
|
||||
if (referencedFilePaths.length > 0) {
|
||||
return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
onProjectUpdateGraph() {
|
||||
this.ensureProjectDependencyGraphUpToDate();
|
||||
}
|
||||
|
||||
private ensureProjectDependencyGraphUpToDate() {
|
||||
if (!this.projectVersionForDependencyGraph || this.project.getProjectVersion() !== this.projectVersionForDependencyGraph) {
|
||||
const currentScriptInfos = this.project.getScriptInfos();
|
||||
for (const scriptInfo of currentScriptInfos) {
|
||||
const fileInfo = this.getOrCreateFileInfo(scriptInfo.path);
|
||||
this.updateFileReferences(fileInfo);
|
||||
}
|
||||
this.forEachFileInfo(fileInfo => {
|
||||
if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) {
|
||||
// This file was deleted from this project
|
||||
fileInfo.removeFileReferences();
|
||||
this.removeFileInfo(fileInfo.scriptInfo.path);
|
||||
}
|
||||
});
|
||||
this.projectVersionForDependencyGraph = this.project.getProjectVersion();
|
||||
}
|
||||
}
|
||||
|
||||
private updateFileReferences(fileInfo: ModuleBuilderFileInfo) {
|
||||
// Only need to update if the content of the file changed.
|
||||
if (fileInfo.scriptVersionForReferences === fileInfo.scriptInfo.getLatestVersion()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newReferences = this.getReferencedFileInfos(fileInfo);
|
||||
const oldReferences = fileInfo.references;
|
||||
|
||||
let oldIndex = 0;
|
||||
let newIndex = 0;
|
||||
while (oldIndex < oldReferences.length && newIndex < newReferences.length) {
|
||||
const oldReference = oldReferences[oldIndex];
|
||||
const newReference = newReferences[newIndex];
|
||||
const compare = ModuleBuilderFileInfo.compareFileInfos(oldReference, newReference);
|
||||
if (compare < 0) {
|
||||
// New reference is greater then current reference. That means
|
||||
// the current reference doesn't exist anymore after parsing. So delete
|
||||
// references.
|
||||
oldReference.removeReferencedBy(fileInfo);
|
||||
oldIndex++;
|
||||
}
|
||||
else if (compare > 0) {
|
||||
// A new reference info. Add it.
|
||||
newReference.addReferencedBy(fileInfo);
|
||||
newIndex++;
|
||||
}
|
||||
else {
|
||||
// Equal. Go to next
|
||||
oldIndex++;
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
// Clean old references
|
||||
for (let i = oldIndex; i < oldReferences.length; i++) {
|
||||
oldReferences[i].removeReferencedBy(fileInfo);
|
||||
}
|
||||
// Update new references
|
||||
for (let i = newIndex; i < newReferences.length; i++) {
|
||||
newReferences[i].addReferencedBy(fileInfo);
|
||||
}
|
||||
|
||||
fileInfo.references = newReferences;
|
||||
fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion();
|
||||
}
|
||||
|
||||
getFilesAffectedBy(scriptInfo: ScriptInfo): string[] {
|
||||
this.ensureProjectDependencyGraphUpToDate();
|
||||
|
||||
const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName];
|
||||
const fileInfo = this.getFileInfo(scriptInfo.path);
|
||||
if (!fileInfo || !fileInfo.updateShapeSignature()) {
|
||||
return singleFileResult;
|
||||
}
|
||||
|
||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
||||
return this.project.getAllEmittableFiles();
|
||||
}
|
||||
|
||||
const options = this.project.getCompilerOptions();
|
||||
if (options && (options.isolatedModules || options.out || options.outFile)) {
|
||||
return singleFileResult;
|
||||
}
|
||||
|
||||
// Now we need to if each file in the referencedBy list has a shape change as well.
|
||||
// Because if so, its own referencedBy files need to be saved as well to make the
|
||||
// emitting result consistent with files on disk.
|
||||
|
||||
// Use slice to clone the array to avoid manipulating in place
|
||||
const queue = fileInfo.referencedBy.slice(0);
|
||||
const fileNameSet = createMap<ScriptInfo>();
|
||||
fileNameSet[scriptInfo.fileName] = scriptInfo;
|
||||
while (queue.length > 0) {
|
||||
const processingFileInfo = queue.pop();
|
||||
if (processingFileInfo.updateShapeSignature() && processingFileInfo.referencedBy.length > 0) {
|
||||
for (const potentialFileInfo of processingFileInfo.referencedBy) {
|
||||
if (!fileNameSet[potentialFileInfo.scriptInfo.fileName]) {
|
||||
queue.push(potentialFileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
fileNameSet[processingFileInfo.scriptInfo.fileName] = processingFileInfo.scriptInfo;
|
||||
}
|
||||
const result: string[] = [];
|
||||
for (const fileName in fileNameSet) {
|
||||
if (shouldEmitFile(fileNameSet[fileName])) {
|
||||
result.push(fileName);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function createBuilder(project: Project): Builder {
|
||||
const moduleKind = project.getCompilerOptions().module;
|
||||
switch (moduleKind) {
|
||||
case ModuleKind.None:
|
||||
return new NonModuleBuilder(project);
|
||||
default:
|
||||
return new ModuleBuilder(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
|
||||
// TODO: extract services types
|
||||
interface HostCancellationToken {
|
||||
isCancellationRequested(): boolean;
|
||||
}
|
||||
|
||||
import fs = require("fs");
|
||||
|
||||
function createCancellationToken(args: string[]): HostCancellationToken {
|
||||
let cancellationPipeName: string;
|
||||
for (let i = 0; i < args.length - 1; i++) {
|
||||
if (args[i] === "--cancellationPipeName") {
|
||||
cancellationPipeName = args[i + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cancellationPipeName) {
|
||||
return { isCancellationRequested: () => false };
|
||||
}
|
||||
return {
|
||||
isCancellationRequested() {
|
||||
try {
|
||||
fs.statSync(cancellationPipeName);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
export = createCancellationToken;
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"pretty": true,
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"cancellationToken.ts"
|
||||
]
|
||||
}
|
||||
+1082
-2334
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="utilities.ts" />
|
||||
/// <reference path="scriptInfo.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
|
||||
private compilationSettings: ts.CompilerOptions;
|
||||
private readonly resolvedModuleNames: ts.FileMap<Map<ResolvedModuleWithFailedLookupLocations>>;
|
||||
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
|
||||
private readonly getCanonicalFileName: (fileName: string) => string;
|
||||
|
||||
private readonly resolveModuleName: typeof resolveModuleName;
|
||||
readonly trace: (s: string) => void;
|
||||
|
||||
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
|
||||
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
|
||||
this.resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
this.resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
|
||||
if (host.trace) {
|
||||
this.trace = s => host.trace(s);
|
||||
}
|
||||
|
||||
this.resolveModuleName = (moduleName, containingFile, compilerOptions, host) => {
|
||||
const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
||||
if (primaryResult.resolvedModule) {
|
||||
// return result immediately only if it is .ts, .tsx or .d.ts
|
||||
// otherwise try to load typings from @types
|
||||
if (fileExtensionIsAny(primaryResult.resolvedModule.resolvedFileName, supportedTypeScriptExtensions)) {
|
||||
return primaryResult;
|
||||
}
|
||||
}
|
||||
// create different collection of failed lookup locations for second pass
|
||||
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
||||
const secondaryLookupFailedLookupLocations: string[] = [];
|
||||
const globalCache = this.project.projectService.typingsInstaller.globalTypingsCacheLocation;
|
||||
if (this.project.getTypingOptions().enableAutoDiscovery && globalCache) {
|
||||
const traceEnabled = isTraceEnabled(compilerOptions, host);
|
||||
if (traceEnabled) {
|
||||
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, this.project.getProjectName(), moduleName, globalCache);
|
||||
}
|
||||
const state: ModuleResolutionState = { compilerOptions, host, skipTsx: false, traceEnabled };
|
||||
const resolvedName = loadModuleFromNodeModules(moduleName, globalCache, secondaryLookupFailedLookupLocations, state, /*checkOneLevel*/ true);
|
||||
if (resolvedName) {
|
||||
return createResolvedModule(resolvedName, /*isExternalLibraryImport*/ true, primaryResult.failedLookupLocations.concat(secondaryLookupFailedLookupLocations));
|
||||
}
|
||||
}
|
||||
if (!primaryResult.resolvedModule && secondaryLookupFailedLookupLocations.length) {
|
||||
primaryResult.failedLookupLocations = primaryResult.failedLookupLocations.concat(secondaryLookupFailedLookupLocations);
|
||||
}
|
||||
return primaryResult;
|
||||
};
|
||||
}
|
||||
|
||||
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
|
||||
names: string[],
|
||||
containingFile: string,
|
||||
cache: ts.FileMap<Map<T>>,
|
||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||
getResult: (s: T) => R): R[] {
|
||||
|
||||
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const currentResolutionsInFile = cache.get(path);
|
||||
|
||||
const newResolutions: Map<T> = createMap<T>();
|
||||
const resolvedModules: R[] = [];
|
||||
const compilerOptions = this.getCompilationSettings();
|
||||
|
||||
for (const name of names) {
|
||||
// check if this is a duplicate entry in the list
|
||||
let resolution = newResolutions[name];
|
||||
if (!resolution) {
|
||||
const existingResolution = currentResolutionsInFile && currentResolutionsInFile[name];
|
||||
if (moduleResolutionIsValid(existingResolution)) {
|
||||
// ok, it is safe to use existing name resolution results
|
||||
resolution = existingResolution;
|
||||
}
|
||||
else {
|
||||
newResolutions[name] = resolution = loader(name, containingFile, compilerOptions, this);
|
||||
}
|
||||
}
|
||||
|
||||
ts.Debug.assert(resolution !== undefined);
|
||||
|
||||
resolvedModules.push(getResult(resolution));
|
||||
}
|
||||
|
||||
// replace old results with a new one
|
||||
cache.set(path, newResolutions);
|
||||
return resolvedModules;
|
||||
|
||||
function moduleResolutionIsValid(resolution: T): boolean {
|
||||
if (!resolution) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getResult(resolution)) {
|
||||
// TODO: consider checking failedLookupLocations
|
||||
return true;
|
||||
}
|
||||
|
||||
// consider situation if we have no candidate locations as valid resolution.
|
||||
// after all there is no point to invalidate it if we have no idea where to look for the module.
|
||||
return resolution.failedLookupLocations.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
getProjectVersion() {
|
||||
return this.project.getProjectVersion();
|
||||
}
|
||||
|
||||
getCompilationSettings() {
|
||||
return this.compilationSettings;
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames() {
|
||||
return this.host.useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
getCancellationToken() {
|
||||
return this.cancellationToken;
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective);
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, m => m.resolvedModule);
|
||||
}
|
||||
|
||||
getDefaultLibFileName() {
|
||||
const nodeModuleBinDir = getDirectoryPath(normalizePath(this.host.getExecutingFilePath()));
|
||||
return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings));
|
||||
}
|
||||
|
||||
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
|
||||
const scriptInfo = this.project.getScriptInfoLSHost(filename);
|
||||
if (scriptInfo) {
|
||||
return scriptInfo.snap();
|
||||
}
|
||||
}
|
||||
|
||||
getScriptFileNames() {
|
||||
return this.project.getRootFilesLSHost();
|
||||
}
|
||||
|
||||
getTypeRootsVersion() {
|
||||
return this.project.typesVersion;
|
||||
}
|
||||
|
||||
getScriptKind(fileName: string) {
|
||||
const info = this.project.getScriptInfoLSHost(fileName);
|
||||
return info && info.scriptKind;
|
||||
}
|
||||
|
||||
getScriptVersion(filename: string) {
|
||||
const info = this.project.getScriptInfoLSHost(filename);
|
||||
return info && info.getLatestVersion();
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return this.host.getCurrentDirectory();
|
||||
}
|
||||
|
||||
resolvePath(path: string): string {
|
||||
return this.host.resolvePath(path);
|
||||
}
|
||||
|
||||
fileExists(path: string): boolean {
|
||||
return this.host.fileExists(path);
|
||||
}
|
||||
|
||||
directoryExists(path: string): boolean {
|
||||
return this.host.directoryExists(path);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
return this.host.readFile(fileName);
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
return this.host.getDirectories(path);
|
||||
}
|
||||
|
||||
notifyFileRemoved(info: ScriptInfo) {
|
||||
this.resolvedModuleNames.remove(info.path);
|
||||
this.resolvedTypeReferenceDirectives.remove(info.path);
|
||||
}
|
||||
|
||||
setCompilationSettings(opt: ts.CompilerOptions) {
|
||||
this.compilationSettings = opt;
|
||||
// conservatively assume that changing compiler options might affect module resolution strategy
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="utilities.ts"/>
|
||||
/// <reference path="scriptInfo.ts"/>
|
||||
/// <reference path="lsHost.ts"/>
|
||||
/// <reference path="typingsCache.ts"/>
|
||||
/// <reference path="builder.ts"/>
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
export enum ProjectKind {
|
||||
Inferred,
|
||||
Configured,
|
||||
External
|
||||
}
|
||||
|
||||
function remove<T>(items: T[], item: T) {
|
||||
const index = items.indexOf(item);
|
||||
if (index >= 0) {
|
||||
items.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function isJsOrDtsFile(info: ScriptInfo) {
|
||||
return info.scriptKind === ScriptKind.JS || info.scriptKind == ScriptKind.JSX || fileExtensionIs(info.fileName, ".d.ts");
|
||||
}
|
||||
|
||||
export function allRootFilesAreJsOrDts(project: Project): boolean {
|
||||
return project.getRootScriptInfos().every(isJsOrDtsFile);
|
||||
}
|
||||
|
||||
export function allFilesAreJsOrDts(project: Project): boolean {
|
||||
return project.getScriptInfos().every(isJsOrDtsFile);
|
||||
}
|
||||
|
||||
export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles {
|
||||
projectErrors: Diagnostic[];
|
||||
}
|
||||
|
||||
export abstract class Project {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
||||
private lsHost: ServerLanguageServiceHost;
|
||||
private program: ts.Program;
|
||||
|
||||
private languageService: LanguageService;
|
||||
builder: Builder;
|
||||
/**
|
||||
* Set of files that was returned from the last call to getChangesSinceVersion.
|
||||
*/
|
||||
private lastReportedFileNames: Map<string>;
|
||||
/**
|
||||
* Last version that was reported.
|
||||
*/
|
||||
private lastReportedVersion = 0;
|
||||
/**
|
||||
* Current project structure version.
|
||||
* This property is changed in 'updateGraph' based on the set of files in program
|
||||
*/
|
||||
private projectStructureVersion = 0;
|
||||
/**
|
||||
* Current version of the project state. It is changed when:
|
||||
* - new root file was added/removed
|
||||
* - edit happen in some file that is currently included in the project.
|
||||
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
|
||||
*/
|
||||
private projectStateVersion = 0;
|
||||
|
||||
private typingFiles: TypingsArray;
|
||||
|
||||
protected projectErrors: Diagnostic[];
|
||||
|
||||
public typesVersion = 0;
|
||||
|
||||
public isJsOnlyProject() {
|
||||
this.updateGraph();
|
||||
return allFilesAreJsOrDts(this);
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
private documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
public languageServiceEnabled: boolean,
|
||||
private compilerOptions: CompilerOptions,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
|
||||
if (!this.compilerOptions) {
|
||||
this.compilerOptions = ts.getDefaultCompilerOptions();
|
||||
this.compilerOptions.allowNonTsExtensions = true;
|
||||
this.compilerOptions.allowJs = true;
|
||||
}
|
||||
else if (hasExplicitListOfFiles) {
|
||||
// If files are listed explicitly, allow all extensions
|
||||
this.compilerOptions.allowNonTsExtensions = true;
|
||||
}
|
||||
|
||||
if (languageServiceEnabled) {
|
||||
this.enableLanguageService();
|
||||
}
|
||||
else {
|
||||
this.disableLanguageService();
|
||||
}
|
||||
|
||||
this.builder = createBuilder(this);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
getProjectErrors() {
|
||||
return this.projectErrors;
|
||||
}
|
||||
|
||||
getLanguageService(ensureSynchronized = true): LanguageService {
|
||||
if (ensureSynchronized) {
|
||||
this.updateGraph();
|
||||
}
|
||||
return this.languageService;
|
||||
}
|
||||
|
||||
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return [];
|
||||
}
|
||||
this.updateGraph();
|
||||
return this.builder.getFilesAffectedBy(scriptInfo);
|
||||
}
|
||||
|
||||
getProjectVersion() {
|
||||
return this.projectStateVersion.toString();
|
||||
}
|
||||
|
||||
enableLanguageService() {
|
||||
const lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
|
||||
lsHost.setCompilationSettings(this.compilerOptions);
|
||||
this.languageService = ts.createLanguageService(lsHost, this.documentRegistry);
|
||||
|
||||
this.lsHost = lsHost;
|
||||
this.languageServiceEnabled = true;
|
||||
}
|
||||
|
||||
disableLanguageService() {
|
||||
this.languageService = nullLanguageService;
|
||||
this.lsHost = nullLanguageServiceHost;
|
||||
this.languageServiceEnabled = false;
|
||||
}
|
||||
|
||||
abstract getProjectName(): string;
|
||||
abstract getProjectRootPath(): string | undefined;
|
||||
abstract getTypingOptions(): TypingOptions;
|
||||
|
||||
getSourceFile(path: Path) {
|
||||
if (!this.program) {
|
||||
return undefined;
|
||||
}
|
||||
return this.program.getSourceFileByPath(path);
|
||||
}
|
||||
|
||||
updateTypes() {
|
||||
this.typesVersion++;
|
||||
this.markAsDirty();
|
||||
this.updateGraph();
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.program) {
|
||||
// if we have a program - release all files that are enlisted in program
|
||||
for (const f of this.program.getSourceFiles()) {
|
||||
const info = this.projectService.getScriptInfo(f.fileName);
|
||||
info.detachFromProject(this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// release all root files
|
||||
for (const root of this.rootFiles) {
|
||||
root.detachFromProject(this);
|
||||
}
|
||||
}
|
||||
this.rootFiles = undefined;
|
||||
this.rootFilesMap = undefined;
|
||||
this.program = undefined;
|
||||
|
||||
// signal language service to release source files acquired from document registry
|
||||
this.languageService.dispose();
|
||||
}
|
||||
|
||||
getCompilerOptions() {
|
||||
return this.compilerOptions;
|
||||
}
|
||||
|
||||
hasRoots() {
|
||||
return this.rootFiles && this.rootFiles.length > 0;
|
||||
}
|
||||
|
||||
getRootFiles() {
|
||||
return this.rootFiles && this.rootFiles.map(info => info.fileName);
|
||||
}
|
||||
|
||||
getRootFilesLSHost() {
|
||||
const result: string[] = [];
|
||||
if (this.rootFiles) {
|
||||
for (const f of this.rootFiles) {
|
||||
result.push(f.fileName);
|
||||
}
|
||||
if (this.typingFiles) {
|
||||
for (const f of this.typingFiles) {
|
||||
result.push(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getRootScriptInfos() {
|
||||
return this.rootFiles;
|
||||
}
|
||||
|
||||
getScriptInfos() {
|
||||
return map(this.program.getSourceFiles(), sourceFile => {
|
||||
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path);
|
||||
if (!scriptInfo) {
|
||||
Debug.assert(false, `scriptInfo for a file '${sourceFile.fileName}' is missing.`);
|
||||
}
|
||||
return scriptInfo;
|
||||
});
|
||||
}
|
||||
|
||||
getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
getFileNames() {
|
||||
if (!this.program) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!this.languageServiceEnabled) {
|
||||
// if language service is disabled assume that all files in program are root files + default library
|
||||
let rootFiles = this.getRootFiles();
|
||||
if (this.compilerOptions) {
|
||||
const defaultLibrary = getDefaultLibFilePath(this.compilerOptions);
|
||||
if (defaultLibrary) {
|
||||
(rootFiles || (rootFiles = [])).push(asNormalizedPath(defaultLibrary));
|
||||
}
|
||||
}
|
||||
return rootFiles;
|
||||
}
|
||||
const sourceFiles = this.program.getSourceFiles();
|
||||
return sourceFiles.map(sourceFile => asNormalizedPath(sourceFile.fileName));
|
||||
}
|
||||
|
||||
getAllEmittableFiles() {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return [];
|
||||
}
|
||||
const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions);
|
||||
const infos = this.getScriptInfos();
|
||||
const result: string[] = [];
|
||||
for (const info of infos) {
|
||||
if (getBaseFileName(info.fileName) !== defaultLibraryFileName && shouldEmitFile(info)) {
|
||||
result.push(info.fileName);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
containsScriptInfo(info: ScriptInfo): boolean {
|
||||
return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined);
|
||||
}
|
||||
|
||||
containsFile(filename: NormalizedPath, requireOpen?: boolean) {
|
||||
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (info && (info.isOpen || !requireOpen)) {
|
||||
return this.containsScriptInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
isRoot(info: ScriptInfo) {
|
||||
return this.rootFilesMap && this.rootFilesMap.contains(info.path);
|
||||
}
|
||||
|
||||
// add a root file to project
|
||||
addRoot(info: ScriptInfo) {
|
||||
if (!this.isRoot(info)) {
|
||||
this.rootFiles.push(info);
|
||||
this.rootFilesMap.set(info.path, info);
|
||||
info.attachToProject(this);
|
||||
|
||||
this.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
removeFile(info: ScriptInfo, detachFromProject = true) {
|
||||
this.removeRootFileIfNecessary(info);
|
||||
this.lsHost.notifyFileRemoved(info);
|
||||
|
||||
if (detachFromProject) {
|
||||
info.detachFromProject(this);
|
||||
}
|
||||
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
markAsDirty() {
|
||||
this.projectStateVersion++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates set of files that contribute to this project
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
*/
|
||||
updateGraph(): boolean {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return true;
|
||||
}
|
||||
let hasChanges = this.updateGraphWorker();
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, hasChanges);
|
||||
if (this.setTypings(cachedTypings)) {
|
||||
hasChanges = this.updateGraphWorker() || hasChanges;
|
||||
}
|
||||
if (hasChanges) {
|
||||
this.projectStructureVersion++;
|
||||
}
|
||||
return !hasChanges;
|
||||
}
|
||||
|
||||
private setTypings(typings: TypingsArray): boolean {
|
||||
if (arrayIsEqualTo(this.typingFiles, typings)) {
|
||||
return false;
|
||||
}
|
||||
this.typingFiles = typings;
|
||||
this.markAsDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateGraphWorker() {
|
||||
const oldProgram = this.program;
|
||||
this.program = this.languageService.getProgram();
|
||||
|
||||
let hasChanges = false;
|
||||
// bump up the version if
|
||||
// - oldProgram is not set - this is a first time updateGraph is called
|
||||
// - newProgram is different from the old program and structure of the old program was not reused.
|
||||
if (!oldProgram || (this.program !== oldProgram && !oldProgram.structureIsReused)) {
|
||||
hasChanges = true;
|
||||
if (oldProgram) {
|
||||
for (const f of oldProgram.getSourceFiles()) {
|
||||
if (this.program.getSourceFileByPath(f.path)) {
|
||||
continue;
|
||||
}
|
||||
// new program does not contain this file - detach it from the project
|
||||
const scriptInfoToDetach = this.projectService.getScriptInfo(f.fileName);
|
||||
if (scriptInfoToDetach) {
|
||||
scriptInfoToDetach.detachFromProject(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.builder.onProjectUpdateGraph();
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
getScriptInfoLSHost(fileName: string) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
||||
if (scriptInfo) {
|
||||
scriptInfo.attachToProject(this);
|
||||
}
|
||||
return scriptInfo;
|
||||
}
|
||||
|
||||
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
|
||||
if (scriptInfo && !scriptInfo.isAttached(this)) {
|
||||
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
|
||||
}
|
||||
return scriptInfo;
|
||||
}
|
||||
|
||||
getScriptInfo(uncheckedFileName: string) {
|
||||
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
|
||||
}
|
||||
|
||||
filesToString() {
|
||||
if (!this.program) {
|
||||
return "";
|
||||
}
|
||||
let strBuilder = "";
|
||||
for (const file of this.program.getSourceFiles()) {
|
||||
strBuilder += `${file.fileName}\n`;
|
||||
}
|
||||
return strBuilder;
|
||||
}
|
||||
|
||||
setCompilerOptions(compilerOptions: CompilerOptions) {
|
||||
if (compilerOptions) {
|
||||
if (this.projectKind === ProjectKind.Inferred) {
|
||||
compilerOptions.allowJs = true;
|
||||
}
|
||||
compilerOptions.allowNonTsExtensions = true;
|
||||
this.compilerOptions = compilerOptions;
|
||||
this.lsHost.setCompilationSettings(compilerOptions);
|
||||
|
||||
this.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
reloadScript(filename: NormalizedPath): boolean {
|
||||
const script = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (script) {
|
||||
Debug.assert(script.isAttached(this));
|
||||
script.reloadFromFile();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
|
||||
this.updateGraph();
|
||||
|
||||
const info = {
|
||||
projectName: this.getProjectName(),
|
||||
version: this.projectStructureVersion,
|
||||
isInferred: this.projectKind === ProjectKind.Inferred,
|
||||
options: this.getCompilerOptions()
|
||||
};
|
||||
// check if requested version is the same that we have reported last time
|
||||
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
|
||||
// if current structure version is the same - return info witout any changes
|
||||
if (this.projectStructureVersion == this.lastReportedVersion) {
|
||||
return { info, projectErrors: this.projectErrors };
|
||||
}
|
||||
// compute and return the difference
|
||||
const lastReportedFileNames = this.lastReportedFileNames;
|
||||
const currentFiles = arrayToMap(this.getFileNames(), x => x);
|
||||
|
||||
const added: string[] = [];
|
||||
const removed: string[] = [];
|
||||
for (const id in currentFiles) {
|
||||
if (!hasProperty(lastReportedFileNames, id)) {
|
||||
added.push(id);
|
||||
}
|
||||
}
|
||||
for (const id in lastReportedFileNames) {
|
||||
if (!hasProperty(currentFiles, id)) {
|
||||
removed.push(id);
|
||||
}
|
||||
}
|
||||
this.lastReportedFileNames = currentFiles;
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
|
||||
}
|
||||
else {
|
||||
// unknown version - return everything
|
||||
const projectFileNames = this.getFileNames();
|
||||
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
return { info, files: projectFileNames, projectErrors: this.projectErrors };
|
||||
}
|
||||
}
|
||||
|
||||
getReferencedFiles(path: Path): Path[] {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const sourceFile = this.getSourceFile(path);
|
||||
if (!sourceFile) {
|
||||
return [];
|
||||
}
|
||||
// We need to use a set here since the code can contain the same import twice,
|
||||
// but that will only be one dependency.
|
||||
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
|
||||
const referencedFiles = createMap<boolean>();
|
||||
if (sourceFile.imports && sourceFile.imports.length > 0) {
|
||||
const checker: TypeChecker = this.program.getTypeChecker();
|
||||
for (const importName of sourceFile.imports) {
|
||||
const symbol = checker.getSymbolAtLocation(importName);
|
||||
if (symbol && symbol.declarations && symbol.declarations[0]) {
|
||||
const declarationSourceFile = symbol.declarations[0].getSourceFile();
|
||||
if (declarationSourceFile) {
|
||||
referencedFiles[declarationSourceFile.path] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const currentDirectory = getDirectoryPath(path);
|
||||
const getCanonicalFileName = createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames);
|
||||
// Handle triple slash references
|
||||
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
|
||||
for (const referencedFile of sourceFile.referencedFiles) {
|
||||
const referencedPath = toPath(referencedFile.fileName, currentDirectory, getCanonicalFileName);
|
||||
referencedFiles[referencedPath] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle type reference directives
|
||||
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
|
||||
for (const typeName in sourceFile.resolvedTypeReferenceDirectiveNames) {
|
||||
const resolvedTypeReferenceDirective = sourceFile.resolvedTypeReferenceDirectiveNames[typeName];
|
||||
if (!resolvedTypeReferenceDirective) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
|
||||
const typeFilePath = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
referencedFiles[typeFilePath] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const allFileNames = map(Object.keys(referencedFiles), key => <Path>key);
|
||||
return filter(allFileNames, file => this.projectService.host.fileExists(file));
|
||||
}
|
||||
|
||||
// remove a root file from project
|
||||
private removeRootFileIfNecessary(info: ScriptInfo): void {
|
||||
if (this.isRoot(info)) {
|
||||
remove(this.rootFiles, info);
|
||||
this.rootFilesMap.remove(info.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InferredProject extends Project {
|
||||
|
||||
private static NextId = 1;
|
||||
|
||||
/**
|
||||
* Unique name that identifies this particular inferred project
|
||||
*/
|
||||
private readonly inferredProjectName: string;
|
||||
|
||||
// Used to keep track of what directories are watched for this project
|
||||
directoriesWatchedForTsconfig: string[] = [];
|
||||
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean, compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.Inferred,
|
||||
projectService,
|
||||
documentRegistry,
|
||||
/*files*/ undefined,
|
||||
languageServiceEnabled,
|
||||
compilerOptions,
|
||||
compileOnSaveEnabled);
|
||||
|
||||
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId);
|
||||
InferredProject.NextId++;
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.inferredProjectName;
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
// Single inferred project does not have a project root.
|
||||
if (this.projectService.useSingleInferredProject) {
|
||||
return undefined;
|
||||
}
|
||||
const rootFiles = this.getRootFiles();
|
||||
return getDirectoryPath(rootFiles[0]);
|
||||
}
|
||||
|
||||
close() {
|
||||
super.close();
|
||||
|
||||
for (const directory of this.directoriesWatchedForTsconfig) {
|
||||
this.projectService.stopWatchingDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
getTypingOptions(): TypingOptions {
|
||||
return {
|
||||
enableAutoDiscovery: allRootFilesAreJsOrDts(this),
|
||||
include: [],
|
||||
exclude: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfiguredProject extends Project {
|
||||
private typingOptions: TypingOptions;
|
||||
private projectFileWatcher: FileWatcher;
|
||||
private directoryWatcher: FileWatcher;
|
||||
private directoriesWatchedForWildcards: Map<FileWatcher>;
|
||||
private typeRootsWatchers: FileWatcher[];
|
||||
|
||||
/** Used for configured projects which may have multiple open roots */
|
||||
openRefCount = 0;
|
||||
|
||||
constructor(readonly configFileName: NormalizedPath,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
compilerOptions: CompilerOptions,
|
||||
private wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
return getDirectoryPath(this.configFileName);
|
||||
}
|
||||
|
||||
setProjectErrors(projectErrors: Diagnostic[]) {
|
||||
this.projectErrors = projectErrors;
|
||||
}
|
||||
|
||||
setTypingOptions(newTypingOptions: TypingOptions): void {
|
||||
this.typingOptions = newTypingOptions;
|
||||
}
|
||||
|
||||
getTypingOptions() {
|
||||
return this.typingOptions;
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.configFileName;
|
||||
}
|
||||
|
||||
watchConfigFile(callback: (project: ConfiguredProject) => void) {
|
||||
this.projectFileWatcher = this.projectService.host.watchFile(this.configFileName, _ => callback(this));
|
||||
}
|
||||
|
||||
watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) {
|
||||
const roots = this.getEffectiveTypeRoots();
|
||||
const watchers: FileWatcher[] = [];
|
||||
for (const root of roots) {
|
||||
this.projectService.logger.info(`Add type root watcher for: ${root}`);
|
||||
watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, path), /*recursive*/ false));
|
||||
}
|
||||
this.typeRootsWatchers = watchers;
|
||||
}
|
||||
|
||||
watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) {
|
||||
if (this.directoryWatcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
const directoryToWatch = getDirectoryPath(this.configFileName);
|
||||
this.projectService.logger.info(`Add recursive watcher for: ${directoryToWatch}`);
|
||||
this.directoryWatcher = this.projectService.host.watchDirectory(directoryToWatch, path => callback(this, path), /*recursive*/ true);
|
||||
}
|
||||
|
||||
watchWildcards(callback: (project: ConfiguredProject, path: string) => void) {
|
||||
if (!this.wildcardDirectories) {
|
||||
return;
|
||||
}
|
||||
const configDirectoryPath = getDirectoryPath(this.configFileName);
|
||||
this.directoriesWatchedForWildcards = reduceProperties(this.wildcardDirectories, (watchers, flag, directory) => {
|
||||
if (comparePaths(configDirectoryPath, directory, ".", !this.projectService.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) {
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
this.projectService.logger.info(`Add ${recursive ? "recursive " : ""}watcher for: ${directory}`);
|
||||
watchers[directory] = this.projectService.host.watchDirectory(
|
||||
directory,
|
||||
path => callback(this, path),
|
||||
recursive
|
||||
);
|
||||
}
|
||||
return watchers;
|
||||
}, <Map<FileWatcher>>{});
|
||||
}
|
||||
|
||||
stopWatchingDirectory() {
|
||||
if (this.directoryWatcher) {
|
||||
this.directoryWatcher.close();
|
||||
this.directoryWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
super.close();
|
||||
|
||||
if (this.projectFileWatcher) {
|
||||
this.projectFileWatcher.close();
|
||||
}
|
||||
|
||||
if (this.typeRootsWatchers) {
|
||||
for (const watcher of this.typeRootsWatchers) {
|
||||
watcher.close();
|
||||
}
|
||||
this.typeRootsWatchers = undefined;
|
||||
}
|
||||
|
||||
for (const id in this.directoriesWatchedForWildcards) {
|
||||
this.directoriesWatchedForWildcards[id].close();
|
||||
}
|
||||
this.directoriesWatchedForWildcards = undefined;
|
||||
|
||||
this.stopWatchingDirectory();
|
||||
}
|
||||
|
||||
addOpenRef() {
|
||||
this.openRefCount++;
|
||||
}
|
||||
|
||||
deleteOpenRef() {
|
||||
this.openRefCount--;
|
||||
return this.openRefCount;
|
||||
}
|
||||
|
||||
getEffectiveTypeRoots() {
|
||||
return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || [];
|
||||
}
|
||||
}
|
||||
|
||||
export class ExternalProject extends Project {
|
||||
private typingOptions: TypingOptions;
|
||||
constructor(readonly externalProjectName: string,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean,
|
||||
private readonly projectFilePath?: string) {
|
||||
super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
if (this.projectFilePath) {
|
||||
return getDirectoryPath(this.projectFilePath);
|
||||
}
|
||||
// if the projectFilePath is not given, we make the assumption that the project name
|
||||
// is the path of the project file. AS the project name is provided by VS, we need to
|
||||
// normalize slashes before using it as a file name.
|
||||
return getDirectoryPath(normalizeSlashes(this.externalProjectName));
|
||||
}
|
||||
|
||||
getTypingOptions() {
|
||||
return this.typingOptions;
|
||||
}
|
||||
|
||||
setProjectErrors(projectErrors: Diagnostic[]) {
|
||||
this.projectErrors = projectErrors;
|
||||
}
|
||||
|
||||
setTypingOptions(newTypingOptions: TypingOptions): void {
|
||||
if (!newTypingOptions) {
|
||||
// set default typings options
|
||||
newTypingOptions = {
|
||||
enableAutoDiscovery: allRootFilesAreJsOrDts(this),
|
||||
include: [],
|
||||
exclude: []
|
||||
};
|
||||
}
|
||||
else {
|
||||
if (newTypingOptions.enableAutoDiscovery === undefined) {
|
||||
// if autoDiscovery was not specified by the caller - set it based on the content of the project
|
||||
newTypingOptions.enableAutoDiscovery = allRootFilesAreJsOrDts(this);
|
||||
}
|
||||
if (!newTypingOptions.include) {
|
||||
newTypingOptions.include = [];
|
||||
}
|
||||
if (!newTypingOptions.exclude) {
|
||||
newTypingOptions.exclude = [];
|
||||
}
|
||||
}
|
||||
this.typingOptions = newTypingOptions;
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.externalProjectName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+211
-7
@@ -91,6 +91,27 @@ declare namespace ts.server.protocol {
|
||||
* The file for the request (absolute pathname required).
|
||||
*/
|
||||
file: string;
|
||||
|
||||
/*
|
||||
* Optional name of project that contains file
|
||||
*/
|
||||
projectFileName?: string;
|
||||
}
|
||||
|
||||
export interface TodoCommentRequest extends FileRequest {
|
||||
arguments: TodoCommentRequestArgs;
|
||||
}
|
||||
|
||||
export interface TodoCommentRequestArgs extends FileRequestArgs {
|
||||
descriptors: TodoCommentDescriptor[];
|
||||
}
|
||||
|
||||
export interface IndentationRequest extends FileLocationRequest {
|
||||
arguments: IndentationRequestArgs;
|
||||
}
|
||||
|
||||
export interface IndentationRequestArgs extends FileLocationRequestArgs {
|
||||
options?: EditorSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +131,14 @@ declare namespace ts.server.protocol {
|
||||
arguments: ProjectInfoRequestArgs;
|
||||
}
|
||||
|
||||
export interface ProjectRequest extends Request {
|
||||
arguments: ProjectRequestArgs;
|
||||
}
|
||||
|
||||
export interface ProjectRequestArgs {
|
||||
projectFileName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response message body for "projectInfo" request
|
||||
*/
|
||||
@@ -129,6 +158,16 @@ declare namespace ts.server.protocol {
|
||||
languageServiceDisabled?: boolean;
|
||||
}
|
||||
|
||||
export interface DiagnosticWithLinePosition {
|
||||
message: string;
|
||||
start: number;
|
||||
length: number;
|
||||
startLocation: Location;
|
||||
endLocation: Location;
|
||||
category: string;
|
||||
code: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response message for "projectInfo" request
|
||||
*/
|
||||
@@ -151,12 +190,17 @@ declare namespace ts.server.protocol {
|
||||
/**
|
||||
* The line number for the request (1-based).
|
||||
*/
|
||||
line: number;
|
||||
line?: number;
|
||||
|
||||
/**
|
||||
* The character offset (on the line) for the request (1-based).
|
||||
*/
|
||||
offset: number;
|
||||
offset?: number;
|
||||
|
||||
/**
|
||||
* Position (can be specified instead of line/offset pair)
|
||||
*/
|
||||
position?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +210,15 @@ declare namespace ts.server.protocol {
|
||||
arguments: FileLocationRequestArgs;
|
||||
}
|
||||
|
||||
export interface FileSpanRequestArgs extends FileRequestArgs {
|
||||
start: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface FileSpanRequest extends FileRequest {
|
||||
arguments: FileSpanRequestArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments in document highlight request; include: filesToSearch, file,
|
||||
* line, offset.
|
||||
@@ -255,6 +308,14 @@ declare namespace ts.server.protocol {
|
||||
body?: FileSpan[];
|
||||
}
|
||||
|
||||
export interface BraceCompletionRequest extends FileLocationRequest {
|
||||
arguments: BraceCompletionRequestArgs;
|
||||
}
|
||||
|
||||
export interface BraceCompletionRequestArgs extends FileLocationRequestArgs {
|
||||
openingBrace: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get occurrences request; value of command field is
|
||||
* "occurrences". Return response giving spans that are relevant
|
||||
@@ -442,6 +503,62 @@ declare namespace ts.server.protocol {
|
||||
body?: RenameResponseBody;
|
||||
}
|
||||
|
||||
export interface ExternalFile {
|
||||
fileName: string;
|
||||
scriptKind?: ScriptKind;
|
||||
hasMixedContent?: boolean;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export interface ExternalProject {
|
||||
projectFileName: string;
|
||||
rootFiles: ExternalFile[];
|
||||
options: ExternalProjectCompilerOptions;
|
||||
typingOptions?: TypingOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* For external projects, some of the project settings are sent together with
|
||||
* compiler settings.
|
||||
*/
|
||||
export interface ExternalProjectCompilerOptions extends CompilerOptions {
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
export interface ProjectVersionInfo {
|
||||
projectName: string;
|
||||
isInferred: boolean;
|
||||
version: number;
|
||||
options: CompilerOptions;
|
||||
}
|
||||
|
||||
export interface ProjectChanges {
|
||||
added: string[];
|
||||
removed: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes set of files in the project.
|
||||
* info might be omitted in case of inferred projects
|
||||
* if files is set - then this is the entire set of files in the project
|
||||
* if changes is set - then this is the set of changes that should be applied to existing project
|
||||
* otherwise - assume that nothing is changed
|
||||
*/
|
||||
export interface ProjectFiles {
|
||||
info?: ProjectVersionInfo;
|
||||
files?: string[];
|
||||
changes?: ProjectChanges;
|
||||
}
|
||||
|
||||
export interface ProjectFilesWithDiagnostics extends ProjectFiles {
|
||||
projectErrors: DiagnosticWithLinePosition[];
|
||||
}
|
||||
|
||||
export interface ChangedOpenFile {
|
||||
fileName: string;
|
||||
changes: ts.TextChange[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Editor options
|
||||
*/
|
||||
@@ -494,9 +611,6 @@ declare namespace ts.server.protocol {
|
||||
|
||||
/** Defines whether an open brace is put onto a new line for control blocks or not. Default value is false. */
|
||||
placeOpenBraceOnNewLineForControlBlocks?: boolean;
|
||||
|
||||
/** Index operator */
|
||||
[key: string]: string | number | boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,6 +633,11 @@ declare namespace ts.server.protocol {
|
||||
* The format options to use during formatting and other code editing features.
|
||||
*/
|
||||
formatOptions?: FormatOptions;
|
||||
|
||||
/**
|
||||
* If set to true - then all loose files will land into one inferred project
|
||||
*/
|
||||
useOneInferredProject?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -564,6 +683,54 @@ declare namespace ts.server.protocol {
|
||||
arguments: OpenRequestArgs;
|
||||
}
|
||||
|
||||
type OpenExternalProjectArgs = ExternalProject;
|
||||
|
||||
export interface OpenExternalProjectRequest extends Request {
|
||||
arguments: OpenExternalProjectArgs;
|
||||
}
|
||||
|
||||
export interface CloseExternalProjectRequestArgs {
|
||||
projectFileName: string;
|
||||
}
|
||||
|
||||
export interface OpenExternalProjectsRequest extends Request {
|
||||
arguments: OpenExternalProjectsArgs;
|
||||
}
|
||||
|
||||
export interface OpenExternalProjectsArgs {
|
||||
projects: ExternalProject[];
|
||||
}
|
||||
|
||||
export interface CloseExternalProjectRequest extends Request {
|
||||
arguments: CloseExternalProjectRequestArgs;
|
||||
}
|
||||
|
||||
export interface SynchronizeProjectListRequest extends Request {
|
||||
arguments: SynchronizeProjectListRequestArgs;
|
||||
}
|
||||
|
||||
export interface SynchronizeProjectListRequestArgs {
|
||||
knownProjects: protocol.ProjectVersionInfo[];
|
||||
}
|
||||
|
||||
export interface ApplyChangedToOpenFilesRequest extends Request {
|
||||
arguments: ApplyChangedToOpenFilesRequestArgs;
|
||||
}
|
||||
|
||||
export interface ApplyChangedToOpenFilesRequestArgs {
|
||||
openFiles?: ExternalFile[];
|
||||
changedFiles?: ChangedOpenFile[];
|
||||
closedFiles?: string[];
|
||||
}
|
||||
|
||||
export interface SetCompilerOptionsForInferredProjectsArgs {
|
||||
options: ExternalProjectCompilerOptions;
|
||||
}
|
||||
|
||||
export interface SetCompilerOptionsForInferredProjectsRequest extends Request {
|
||||
arguments: SetCompilerOptionsForInferredProjectsArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit request; value of command field is "exit". Ask the server process
|
||||
* to exit.
|
||||
@@ -581,6 +748,26 @@ declare namespace ts.server.protocol {
|
||||
export interface CloseRequest extends FileRequest {
|
||||
}
|
||||
|
||||
export interface CompileOnSaveAffectedFileListRequest extends FileRequest {
|
||||
}
|
||||
|
||||
export interface CompileOnSaveAffectedFileListSingleProject {
|
||||
projectFileName: string;
|
||||
fileNames: string[];
|
||||
}
|
||||
|
||||
export interface CompileOnSaveAffectedFileListResponse extends Response {
|
||||
body: CompileOnSaveAffectedFileListSingleProject[];
|
||||
}
|
||||
|
||||
export interface CompileOnSaveEmitFileRequest extends FileRequest {
|
||||
args: CompileOnSaveEmitFileRequestArgs;
|
||||
}
|
||||
|
||||
export interface CompileOnSaveEmitFileRequestArgs extends FileRequestArgs {
|
||||
forced?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickinfo request; value of command field is
|
||||
* "quickinfo". Return response giving a quick type and
|
||||
@@ -645,6 +832,9 @@ declare namespace ts.server.protocol {
|
||||
* Character offset on last line of range for which to format text in file.
|
||||
*/
|
||||
endOffset: number;
|
||||
|
||||
endPosition?: number;
|
||||
options?: ts.FormatCodeOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -698,6 +888,8 @@ declare namespace ts.server.protocol {
|
||||
* Key pressed (';', '\n', or '}').
|
||||
*/
|
||||
key: string;
|
||||
|
||||
options?: ts.FormatCodeOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -949,26 +1141,36 @@ declare namespace ts.server.protocol {
|
||||
* Synchronous request for semantic diagnostics of one file.
|
||||
*/
|
||||
export interface SemanticDiagnosticsSyncRequest extends FileRequest {
|
||||
arguments: SemanticDiagnosticsSyncRequestArgs;
|
||||
}
|
||||
|
||||
export interface SemanticDiagnosticsSyncRequestArgs extends FileRequestArgs {
|
||||
includeLinePosition?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response object for synchronous sematic diagnostics request.
|
||||
*/
|
||||
export interface SemanticDiagnosticsSyncResponse extends Response {
|
||||
body?: Diagnostic[];
|
||||
body?: Diagnostic[] | DiagnosticWithLinePosition[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous request for syntactic diagnostics of one file.
|
||||
*/
|
||||
export interface SyntacticDiagnosticsSyncRequest extends FileRequest {
|
||||
arguments: SyntacticDiagnosticsSyncRequestArgs;
|
||||
}
|
||||
|
||||
export interface SyntacticDiagnosticsSyncRequestArgs extends FileRequestArgs {
|
||||
includeLinePosition?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response object for synchronous syntactic diagnostics request.
|
||||
*/
|
||||
export interface SyntacticDiagnosticsSyncResponse extends Response {
|
||||
body?: Diagnostic[];
|
||||
body?: Diagnostic[] | DiagnosticWithLinePosition[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1161,6 +1363,8 @@ declare namespace ts.server.protocol {
|
||||
* or the entire project.
|
||||
*/
|
||||
currentFileOnly?: boolean;
|
||||
|
||||
projectFileName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
/// <reference path="scriptVersionCache.ts"/>
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
export class ScriptInfo {
|
||||
/**
|
||||
* All projects that include this file
|
||||
*/
|
||||
readonly containingProjects: Project[] = [];
|
||||
private formatCodeSettings: ts.FormatCodeSettings;
|
||||
readonly path: Path;
|
||||
|
||||
private fileWatcher: FileWatcher;
|
||||
private svc: ScriptVersionCache;
|
||||
|
||||
// TODO: allow to update hasMixedContent from the outside
|
||||
constructor(
|
||||
private readonly host: ServerHost,
|
||||
readonly fileName: NormalizedPath,
|
||||
content: string,
|
||||
readonly scriptKind: ScriptKind,
|
||||
public isOpen = false,
|
||||
public hasMixedContent = false) {
|
||||
|
||||
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
|
||||
this.svc = ScriptVersionCache.fromString(host, content);
|
||||
this.scriptKind = scriptKind
|
||||
? scriptKind
|
||||
: getScriptKindFromFileName(fileName);
|
||||
}
|
||||
|
||||
getFormatCodeSettings() {
|
||||
return this.formatCodeSettings;
|
||||
}
|
||||
|
||||
attachToProject(project: Project): boolean {
|
||||
const isNew = !this.isAttached(project);
|
||||
if (isNew) {
|
||||
this.containingProjects.push(project);
|
||||
}
|
||||
return isNew;
|
||||
}
|
||||
|
||||
isAttached(project: Project) {
|
||||
// unrolled for common cases
|
||||
switch (this.containingProjects.length) {
|
||||
case 0: return false;
|
||||
case 1: return this.containingProjects[0] === project;
|
||||
case 2: return this.containingProjects[0] === project || this.containingProjects[1] === project;
|
||||
default: return contains(this.containingProjects, project);
|
||||
}
|
||||
}
|
||||
|
||||
detachFromProject(project: Project) {
|
||||
// unrolled for common cases
|
||||
switch (this.containingProjects.length) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
if (this.containingProjects[0] === project) {
|
||||
this.containingProjects.pop();
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (this.containingProjects[0] === project) {
|
||||
this.containingProjects[0] = this.containingProjects.pop();
|
||||
}
|
||||
else if (this.containingProjects[1] === project) {
|
||||
this.containingProjects.pop();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
removeItemFromSet(this.containingProjects, project);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
detachAllProjects() {
|
||||
for (const p of this.containingProjects) {
|
||||
// detach is unnecessary since we'll clean the list of containing projects anyways
|
||||
p.removeFile(this, /*detachFromProjects*/ false);
|
||||
}
|
||||
this.containingProjects.length = 0;
|
||||
}
|
||||
|
||||
getDefaultProject() {
|
||||
if (this.containingProjects.length === 0) {
|
||||
return Errors.ThrowNoProject();
|
||||
}
|
||||
Debug.assert(this.containingProjects.length !== 0);
|
||||
return this.containingProjects[0];
|
||||
}
|
||||
|
||||
setFormatOptions(formatSettings: protocol.FormatOptions): void {
|
||||
if (formatSettings) {
|
||||
if (!this.formatCodeSettings) {
|
||||
this.formatCodeSettings = getDefaultFormatCodeSettings(this.host);
|
||||
}
|
||||
mergeMaps(this.formatCodeSettings, formatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
setWatcher(watcher: FileWatcher): void {
|
||||
this.stopWatcher();
|
||||
this.fileWatcher = watcher;
|
||||
}
|
||||
|
||||
stopWatcher() {
|
||||
if (this.fileWatcher) {
|
||||
this.fileWatcher.close();
|
||||
this.fileWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getLatestVersion() {
|
||||
return this.svc.latestVersion().toString();
|
||||
}
|
||||
|
||||
reload(script: string) {
|
||||
this.svc.reload(script);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
saveTo(fileName: string) {
|
||||
const snap = this.snap();
|
||||
this.host.writeFile(fileName, snap.getText(0, snap.getLength()));
|
||||
}
|
||||
|
||||
reloadFromFile() {
|
||||
if (this.hasMixedContent) {
|
||||
this.reload("");
|
||||
}
|
||||
else {
|
||||
this.svc.reloadFromFile(this.fileName);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
snap() {
|
||||
return this.svc.getSnapshot();
|
||||
}
|
||||
|
||||
getLineInfo(line: number) {
|
||||
const snap = this.snap();
|
||||
return snap.index.lineNumberToInfo(line);
|
||||
}
|
||||
|
||||
editContent(start: number, end: number, newText: string): void {
|
||||
this.svc.edit(start, end - start, newText);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
markContainingProjectsAsDirty() {
|
||||
for (const p of this.containingProjects) {
|
||||
p.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1 based index
|
||||
*/
|
||||
lineToTextSpan(line: number) {
|
||||
const index = this.snap().index;
|
||||
const lineInfo = index.lineNumberToInfo(line + 1);
|
||||
let len: number;
|
||||
if (lineInfo.leaf) {
|
||||
len = lineInfo.leaf.text.length;
|
||||
}
|
||||
else {
|
||||
const nextLineInfo = index.lineNumberToInfo(line + 2);
|
||||
len = nextLineInfo.offset - lineInfo.offset;
|
||||
}
|
||||
return ts.createTextSpan(lineInfo.offset, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1 based index
|
||||
* @param offset 1 based index
|
||||
*/
|
||||
lineOffsetToPosition(line: number, offset: number): number {
|
||||
const index = this.snap().index;
|
||||
|
||||
const lineInfo = index.lineNumberToInfo(line);
|
||||
// TODO: assert this offset is actually on the line
|
||||
return (lineInfo.offset + offset - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1-based index
|
||||
* @param offset 1-based index
|
||||
*/
|
||||
positionToLineOffset(position: number): ILineInfo {
|
||||
const index = this.snap().index;
|
||||
const lineOffset = index.charOffsetToLineNumberAndPos(position);
|
||||
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,951 @@
|
||||
/// <reference path="..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="protocol.d.ts" />
|
||||
/// <reference path="session.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
const lineCollectionCapacity = 4;
|
||||
|
||||
export interface LineCollection {
|
||||
charCount(): number;
|
||||
lineCount(): number;
|
||||
isLeaf(): boolean;
|
||||
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void;
|
||||
}
|
||||
|
||||
export interface ILineInfo {
|
||||
line: number;
|
||||
offset: number;
|
||||
text?: string;
|
||||
leaf?: LineLeaf;
|
||||
}
|
||||
|
||||
export enum CharRangeSection {
|
||||
PreStart,
|
||||
Start,
|
||||
Entire,
|
||||
Mid,
|
||||
End,
|
||||
PostEnd
|
||||
}
|
||||
|
||||
export interface ILineIndexWalker {
|
||||
goSubtree: boolean;
|
||||
done: boolean;
|
||||
leaf(relativeStart: number, relativeLength: number, lineCollection: LineLeaf): void;
|
||||
pre?(relativeStart: number, relativeLength: number, lineCollection: LineCollection,
|
||||
parent: LineNode, nodeType: CharRangeSection): LineCollection;
|
||||
post?(relativeStart: number, relativeLength: number, lineCollection: LineCollection,
|
||||
parent: LineNode, nodeType: CharRangeSection): LineCollection;
|
||||
}
|
||||
|
||||
class BaseLineIndexWalker implements ILineIndexWalker {
|
||||
goSubtree = true;
|
||||
done = false;
|
||||
leaf(rangeStart: number, rangeLength: number, ll: LineLeaf) {
|
||||
}
|
||||
}
|
||||
|
||||
class EditWalker extends BaseLineIndexWalker {
|
||||
lineIndex = new LineIndex();
|
||||
// path to start of range
|
||||
startPath: LineCollection[];
|
||||
endBranch: LineCollection[] = [];
|
||||
branchNode: LineNode;
|
||||
// path to current node
|
||||
stack: LineNode[];
|
||||
state = CharRangeSection.Entire;
|
||||
lineCollectionAtBranch: LineCollection;
|
||||
initialText = "";
|
||||
trailingText = "";
|
||||
suppressTrailingText = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.lineIndex.root = new LineNode();
|
||||
this.startPath = [this.lineIndex.root];
|
||||
this.stack = [this.lineIndex.root];
|
||||
}
|
||||
|
||||
insertLines(insertedText: string) {
|
||||
if (this.suppressTrailingText) {
|
||||
this.trailingText = "";
|
||||
}
|
||||
if (insertedText) {
|
||||
insertedText = this.initialText + insertedText + this.trailingText;
|
||||
}
|
||||
else {
|
||||
insertedText = this.initialText + this.trailingText;
|
||||
}
|
||||
const lm = LineIndex.linesFromText(insertedText);
|
||||
const lines = lm.lines;
|
||||
if (lines.length > 1) {
|
||||
if (lines[lines.length - 1] == "") {
|
||||
lines.length--;
|
||||
}
|
||||
}
|
||||
let branchParent: LineNode;
|
||||
let lastZeroCount: LineCollection;
|
||||
|
||||
for (let k = this.endBranch.length - 1; k >= 0; k--) {
|
||||
(<LineNode>this.endBranch[k]).updateCounts();
|
||||
if (this.endBranch[k].charCount() === 0) {
|
||||
lastZeroCount = this.endBranch[k];
|
||||
if (k > 0) {
|
||||
branchParent = <LineNode>this.endBranch[k - 1];
|
||||
}
|
||||
else {
|
||||
branchParent = this.branchNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastZeroCount) {
|
||||
branchParent.remove(lastZeroCount);
|
||||
}
|
||||
|
||||
// path at least length two (root and leaf)
|
||||
let insertionNode = <LineNode>this.startPath[this.startPath.length - 2];
|
||||
const leafNode = <LineLeaf>this.startPath[this.startPath.length - 1];
|
||||
const len = lines.length;
|
||||
|
||||
if (len > 0) {
|
||||
leafNode.text = lines[0];
|
||||
|
||||
if (len > 1) {
|
||||
let insertedNodes = <LineCollection[]>new Array(len - 1);
|
||||
let startNode = <LineCollection>leafNode;
|
||||
for (let i = 1, len = lines.length; i < len; i++) {
|
||||
insertedNodes[i - 1] = new LineLeaf(lines[i]);
|
||||
}
|
||||
let pathIndex = this.startPath.length - 2;
|
||||
while (pathIndex >= 0) {
|
||||
insertionNode = <LineNode>this.startPath[pathIndex];
|
||||
insertedNodes = insertionNode.insertAt(startNode, insertedNodes);
|
||||
pathIndex--;
|
||||
startNode = insertionNode;
|
||||
}
|
||||
let insertedNodesLen = insertedNodes.length;
|
||||
while (insertedNodesLen > 0) {
|
||||
const newRoot = new LineNode();
|
||||
newRoot.add(this.lineIndex.root);
|
||||
insertedNodes = newRoot.insertAt(this.lineIndex.root, insertedNodes);
|
||||
insertedNodesLen = insertedNodes.length;
|
||||
this.lineIndex.root = newRoot;
|
||||
}
|
||||
this.lineIndex.root.updateCounts();
|
||||
}
|
||||
else {
|
||||
for (let j = this.startPath.length - 2; j >= 0; j--) {
|
||||
(<LineNode>this.startPath[j]).updateCounts();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// no content for leaf node, so delete it
|
||||
insertionNode.remove(leafNode);
|
||||
for (let j = this.startPath.length - 2; j >= 0; j--) {
|
||||
(<LineNode>this.startPath[j]).updateCounts();
|
||||
}
|
||||
}
|
||||
|
||||
return this.lineIndex;
|
||||
}
|
||||
|
||||
post(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection): LineCollection {
|
||||
// have visited the path for start of range, now looking for end
|
||||
// if range is on single line, we will never make this state transition
|
||||
if (lineCollection === this.lineCollectionAtBranch) {
|
||||
this.state = CharRangeSection.End;
|
||||
}
|
||||
// always pop stack because post only called when child has been visited
|
||||
this.stack.length--;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
pre(relativeStart: number, relativeLength: number, lineCollection: LineCollection, parent: LineCollection, nodeType: CharRangeSection) {
|
||||
// currentNode corresponds to parent, but in the new tree
|
||||
const currentNode = this.stack[this.stack.length - 1];
|
||||
|
||||
if ((this.state === CharRangeSection.Entire) && (nodeType === CharRangeSection.Start)) {
|
||||
// if range is on single line, we will never make this state transition
|
||||
this.state = CharRangeSection.Start;
|
||||
this.branchNode = currentNode;
|
||||
this.lineCollectionAtBranch = lineCollection;
|
||||
}
|
||||
|
||||
let child: LineCollection;
|
||||
function fresh(node: LineCollection): LineCollection {
|
||||
if (node.isLeaf()) {
|
||||
return new LineLeaf("");
|
||||
}
|
||||
else return new LineNode();
|
||||
}
|
||||
switch (nodeType) {
|
||||
case CharRangeSection.PreStart:
|
||||
this.goSubtree = false;
|
||||
if (this.state !== CharRangeSection.End) {
|
||||
currentNode.add(lineCollection);
|
||||
}
|
||||
break;
|
||||
case CharRangeSection.Start:
|
||||
if (this.state === CharRangeSection.End) {
|
||||
this.goSubtree = false;
|
||||
}
|
||||
else {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.startPath[this.startPath.length] = child;
|
||||
}
|
||||
break;
|
||||
case CharRangeSection.Entire:
|
||||
if (this.state !== CharRangeSection.End) {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.startPath[this.startPath.length] = child;
|
||||
}
|
||||
else {
|
||||
if (!lineCollection.isLeaf()) {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.endBranch[this.endBranch.length] = child;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CharRangeSection.Mid:
|
||||
this.goSubtree = false;
|
||||
break;
|
||||
case CharRangeSection.End:
|
||||
if (this.state !== CharRangeSection.End) {
|
||||
this.goSubtree = false;
|
||||
}
|
||||
else {
|
||||
if (!lineCollection.isLeaf()) {
|
||||
child = fresh(lineCollection);
|
||||
currentNode.add(child);
|
||||
this.endBranch[this.endBranch.length] = child;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CharRangeSection.PostEnd:
|
||||
this.goSubtree = false;
|
||||
if (this.state !== CharRangeSection.Start) {
|
||||
currentNode.add(lineCollection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this.goSubtree) {
|
||||
this.stack[this.stack.length] = <LineNode>child;
|
||||
}
|
||||
return lineCollection;
|
||||
}
|
||||
// just gather text from the leaves
|
||||
leaf(relativeStart: number, relativeLength: number, ll: LineLeaf) {
|
||||
if (this.state === CharRangeSection.Start) {
|
||||
this.initialText = ll.text.substring(0, relativeStart);
|
||||
}
|
||||
else if (this.state === CharRangeSection.Entire) {
|
||||
this.initialText = ll.text.substring(0, relativeStart);
|
||||
this.trailingText = ll.text.substring(relativeStart + relativeLength);
|
||||
}
|
||||
else {
|
||||
// state is CharRangeSection.End
|
||||
this.trailingText = ll.text.substring(relativeStart + relativeLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// text change information
|
||||
export class TextChange {
|
||||
constructor(public pos: number, public deleteLen: number, public insertedText?: string) {
|
||||
}
|
||||
|
||||
getTextChangeRange() {
|
||||
return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen),
|
||||
this.insertedText ? this.insertedText.length : 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptVersionCache {
|
||||
changes: TextChange[] = [];
|
||||
versions: LineIndexSnapshot[] = new Array<LineIndexSnapshot>(ScriptVersionCache.maxVersions);
|
||||
minVersion = 0; // no versions earlier than min version will maintain change history
|
||||
|
||||
private host: ServerHost;
|
||||
private currentVersion = 0;
|
||||
|
||||
static changeNumberThreshold = 8;
|
||||
static changeLengthThreshold = 256;
|
||||
static maxVersions = 8;
|
||||
|
||||
private versionToIndex(version: number) {
|
||||
if (version < this.minVersion || version > this.currentVersion) {
|
||||
return undefined;
|
||||
}
|
||||
return version % ScriptVersionCache.maxVersions;
|
||||
}
|
||||
|
||||
private currentVersionToIndex() {
|
||||
return this.currentVersion % ScriptVersionCache.maxVersions;
|
||||
}
|
||||
|
||||
// REVIEW: can optimize by coalescing simple edits
|
||||
edit(pos: number, deleteLen: number, insertedText?: string) {
|
||||
this.changes[this.changes.length] = new TextChange(pos, deleteLen, insertedText);
|
||||
if ((this.changes.length > ScriptVersionCache.changeNumberThreshold) ||
|
||||
(deleteLen > ScriptVersionCache.changeLengthThreshold) ||
|
||||
(insertedText && (insertedText.length > ScriptVersionCache.changeLengthThreshold))) {
|
||||
this.getSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
latest() {
|
||||
return this.versions[this.currentVersionToIndex()];
|
||||
}
|
||||
|
||||
latestVersion() {
|
||||
if (this.changes.length > 0) {
|
||||
this.getSnapshot();
|
||||
}
|
||||
return this.currentVersion;
|
||||
}
|
||||
|
||||
reloadFromFile(filename: string) {
|
||||
let content = this.host.readFile(filename);
|
||||
// If the file doesn't exist or cannot be read, we should
|
||||
// wipe out its cached content on the server to avoid side effects.
|
||||
if (!content) {
|
||||
content = "";
|
||||
}
|
||||
this.reload(content);
|
||||
}
|
||||
|
||||
// reload whole script, leaving no change history behind reload
|
||||
reload(script: string) {
|
||||
this.currentVersion++;
|
||||
this.changes = []; // history wiped out by reload
|
||||
const snap = new LineIndexSnapshot(this.currentVersion, this);
|
||||
|
||||
// delete all versions
|
||||
for (let i = 0; i < this.versions.length; i++) {
|
||||
this.versions[i] = undefined;
|
||||
}
|
||||
|
||||
this.versions[this.currentVersionToIndex()] = snap;
|
||||
snap.index = new LineIndex();
|
||||
const lm = LineIndex.linesFromText(script);
|
||||
snap.index.load(lm.lines);
|
||||
|
||||
this.minVersion = this.currentVersion;
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
let snap = this.versions[this.currentVersionToIndex()];
|
||||
if (this.changes.length > 0) {
|
||||
let snapIndex = snap.index;
|
||||
for (let i = 0, len = this.changes.length; i < len; i++) {
|
||||
const change = this.changes[i];
|
||||
snapIndex = snapIndex.edit(change.pos, change.deleteLen, change.insertedText);
|
||||
}
|
||||
snap = new LineIndexSnapshot(this.currentVersion + 1, this);
|
||||
snap.index = snapIndex;
|
||||
snap.changesSincePreviousVersion = this.changes;
|
||||
|
||||
this.currentVersion = snap.version;
|
||||
this.versions[this.currentVersionToIndex()] = snap;
|
||||
this.changes = [];
|
||||
|
||||
if ((this.currentVersion - this.minVersion) >= ScriptVersionCache.maxVersions) {
|
||||
this.minVersion = (this.currentVersion - ScriptVersionCache.maxVersions) + 1;
|
||||
}
|
||||
}
|
||||
return snap;
|
||||
}
|
||||
|
||||
getTextChangesBetweenVersions(oldVersion: number, newVersion: number) {
|
||||
if (oldVersion < newVersion) {
|
||||
if (oldVersion >= this.minVersion) {
|
||||
const textChangeRanges: ts.TextChangeRange[] = [];
|
||||
for (let i = oldVersion + 1; i <= newVersion; i++) {
|
||||
const snap = this.versions[this.versionToIndex(i)];
|
||||
for (let j = 0, len = snap.changesSincePreviousVersion.length; j < len; j++) {
|
||||
const textChange = snap.changesSincePreviousVersion[j];
|
||||
textChangeRanges[textChangeRanges.length] = textChange.getTextChangeRange();
|
||||
}
|
||||
}
|
||||
return ts.collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ts.unchangedTextChangeRange;
|
||||
}
|
||||
}
|
||||
|
||||
static fromString(host: ServerHost, script: string) {
|
||||
const svc = new ScriptVersionCache();
|
||||
const snap = new LineIndexSnapshot(0, svc);
|
||||
svc.versions[svc.currentVersion] = snap;
|
||||
svc.host = host;
|
||||
snap.index = new LineIndex();
|
||||
const lm = LineIndex.linesFromText(script);
|
||||
snap.index.load(lm.lines);
|
||||
return svc;
|
||||
}
|
||||
}
|
||||
|
||||
export class LineIndexSnapshot implements ts.IScriptSnapshot {
|
||||
index: LineIndex;
|
||||
changesSincePreviousVersion: TextChange[] = [];
|
||||
|
||||
constructor(public version: number, public cache: ScriptVersionCache) {
|
||||
}
|
||||
|
||||
getText(rangeStart: number, rangeEnd: number) {
|
||||
return this.index.getText(rangeStart, rangeEnd - rangeStart);
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this.index.root.charCount();
|
||||
}
|
||||
|
||||
// this requires linear space so don't hold on to these
|
||||
getLineStartPositions(): number[] {
|
||||
const starts: number[] = [-1];
|
||||
let count = 1;
|
||||
let pos = 0;
|
||||
this.index.every((ll, s, len) => {
|
||||
starts[count] = pos;
|
||||
count++;
|
||||
pos += ll.text.length;
|
||||
return true;
|
||||
}, 0);
|
||||
return starts;
|
||||
}
|
||||
|
||||
getLineMapper() {
|
||||
return (line: number) => {
|
||||
return this.index.lineNumberToInfo(line).offset;
|
||||
};
|
||||
}
|
||||
|
||||
getTextChangeRangeSinceVersion(scriptVersion: number) {
|
||||
if (this.version <= scriptVersion) {
|
||||
return ts.unchangedTextChangeRange;
|
||||
}
|
||||
else {
|
||||
return this.cache.getTextChangesBetweenVersions(scriptVersion, this.version);
|
||||
}
|
||||
}
|
||||
getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
|
||||
const oldSnap = <LineIndexSnapshot>oldSnapshot;
|
||||
return this.getTextChangeRangeSinceVersion(oldSnap.version);
|
||||
}
|
||||
}
|
||||
|
||||
export class LineIndex {
|
||||
root: LineNode;
|
||||
// set this to true to check each edit for accuracy
|
||||
checkEdits = false;
|
||||
|
||||
charOffsetToLineNumberAndPos(charOffset: number) {
|
||||
return this.root.charOffsetToLineNumberAndPos(1, charOffset);
|
||||
}
|
||||
|
||||
lineNumberToInfo(lineNumber: number): ILineInfo {
|
||||
const lineCount = this.root.lineCount();
|
||||
if (lineNumber <= lineCount) {
|
||||
const lineInfo = this.root.lineNumberToInfo(lineNumber, 0);
|
||||
lineInfo.line = lineNumber;
|
||||
return lineInfo;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: this.root.charCount()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
load(lines: string[]) {
|
||||
if (lines.length > 0) {
|
||||
const leaves: LineLeaf[] = [];
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
leaves[i] = new LineLeaf(lines[i]);
|
||||
}
|
||||
this.root = LineIndex.buildTreeFromBottom(leaves);
|
||||
}
|
||||
else {
|
||||
this.root = new LineNode();
|
||||
}
|
||||
}
|
||||
|
||||
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
|
||||
this.root.walk(rangeStart, rangeLength, walkFns);
|
||||
}
|
||||
|
||||
getText(rangeStart: number, rangeLength: number) {
|
||||
let accum = "";
|
||||
if ((rangeLength > 0) && (rangeStart < this.root.charCount())) {
|
||||
this.walk(rangeStart, rangeLength, {
|
||||
goSubtree: true,
|
||||
done: false,
|
||||
leaf: (relativeStart: number, relativeLength: number, ll: LineLeaf) => {
|
||||
accum = accum.concat(ll.text.substring(relativeStart, relativeStart + relativeLength));
|
||||
}
|
||||
});
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
|
||||
getLength(): number {
|
||||
return this.root.charCount();
|
||||
}
|
||||
|
||||
every(f: (ll: LineLeaf, s: number, len: number) => boolean, rangeStart: number, rangeEnd?: number) {
|
||||
if (!rangeEnd) {
|
||||
rangeEnd = this.root.charCount();
|
||||
}
|
||||
const walkFns = {
|
||||
goSubtree: true,
|
||||
done: false,
|
||||
leaf: function (this: ILineIndexWalker, relativeStart: number, relativeLength: number, ll: LineLeaf) {
|
||||
if (!f(ll, relativeStart, relativeLength)) {
|
||||
this.done = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.walk(rangeStart, rangeEnd - rangeStart, walkFns);
|
||||
return !walkFns.done;
|
||||
}
|
||||
|
||||
edit(pos: number, deleteLength: number, newText?: string) {
|
||||
function editFlat(source: string, s: number, dl: number, nt = "") {
|
||||
return source.substring(0, s) + nt + source.substring(s + dl, source.length);
|
||||
}
|
||||
if (this.root.charCount() === 0) {
|
||||
// TODO: assert deleteLength === 0
|
||||
if (newText !== undefined) {
|
||||
this.load(LineIndex.linesFromText(newText).lines);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let checkText: string;
|
||||
if (this.checkEdits) {
|
||||
checkText = editFlat(this.getText(0, this.root.charCount()), pos, deleteLength, newText);
|
||||
}
|
||||
const walker = new EditWalker();
|
||||
if (pos >= this.root.charCount()) {
|
||||
// insert at end
|
||||
pos = this.root.charCount() - 1;
|
||||
const endString = this.getText(pos, 1);
|
||||
if (newText) {
|
||||
newText = endString + newText;
|
||||
}
|
||||
else {
|
||||
newText = endString;
|
||||
}
|
||||
deleteLength = 0;
|
||||
walker.suppressTrailingText = true;
|
||||
}
|
||||
else if (deleteLength > 0) {
|
||||
// check whether last characters deleted are line break
|
||||
const e = pos + deleteLength;
|
||||
const lineInfo = this.charOffsetToLineNumberAndPos(e);
|
||||
if ((lineInfo && (lineInfo.offset === 0))) {
|
||||
// move range end just past line that will merge with previous line
|
||||
deleteLength += lineInfo.text.length;
|
||||
// store text by appending to end of insertedText
|
||||
if (newText) {
|
||||
newText = newText + lineInfo.text;
|
||||
}
|
||||
else {
|
||||
newText = lineInfo.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pos < this.root.charCount()) {
|
||||
this.root.walk(pos, deleteLength, walker);
|
||||
walker.insertLines(newText);
|
||||
}
|
||||
if (this.checkEdits) {
|
||||
const updatedText = this.getText(0, this.root.charCount());
|
||||
Debug.assert(checkText == updatedText, "buffer edit mismatch");
|
||||
}
|
||||
return walker.lineIndex;
|
||||
}
|
||||
}
|
||||
|
||||
static buildTreeFromBottom(nodes: LineCollection[]): LineNode {
|
||||
const nodeCount = Math.ceil(nodes.length / lineCollectionCapacity);
|
||||
const interiorNodes: LineNode[] = [];
|
||||
let nodeIndex = 0;
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
interiorNodes[i] = new LineNode();
|
||||
let charCount = 0;
|
||||
let lineCount = 0;
|
||||
for (let j = 0; j < lineCollectionCapacity; j++) {
|
||||
if (nodeIndex < nodes.length) {
|
||||
interiorNodes[i].add(nodes[nodeIndex]);
|
||||
charCount += nodes[nodeIndex].charCount();
|
||||
lineCount += nodes[nodeIndex].lineCount();
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
nodeIndex++;
|
||||
}
|
||||
interiorNodes[i].totalChars = charCount;
|
||||
interiorNodes[i].totalLines = lineCount;
|
||||
}
|
||||
if (interiorNodes.length === 1) {
|
||||
return interiorNodes[0];
|
||||
}
|
||||
else {
|
||||
return this.buildTreeFromBottom(interiorNodes);
|
||||
}
|
||||
}
|
||||
|
||||
static linesFromText(text: string) {
|
||||
const lineStarts = ts.computeLineStarts(text);
|
||||
|
||||
if (lineStarts.length === 0) {
|
||||
return { lines: <string[]>[], lineMap: lineStarts };
|
||||
}
|
||||
const lines = <string[]>new Array(lineStarts.length);
|
||||
const lc = lineStarts.length - 1;
|
||||
for (let lmi = 0; lmi < lc; lmi++) {
|
||||
lines[lmi] = text.substring(lineStarts[lmi], lineStarts[lmi + 1]);
|
||||
}
|
||||
|
||||
const endText = text.substring(lineStarts[lc]);
|
||||
if (endText.length > 0) {
|
||||
lines[lc] = endText;
|
||||
}
|
||||
else {
|
||||
lines.length--;
|
||||
}
|
||||
return { lines: lines, lineMap: lineStarts };
|
||||
}
|
||||
}
|
||||
|
||||
export class LineNode implements LineCollection {
|
||||
totalChars = 0;
|
||||
totalLines = 0;
|
||||
children: LineCollection[] = [];
|
||||
|
||||
isLeaf() {
|
||||
return false;
|
||||
}
|
||||
|
||||
updateCounts() {
|
||||
this.totalChars = 0;
|
||||
this.totalLines = 0;
|
||||
for (let i = 0, len = this.children.length; i < len; i++) {
|
||||
const child = this.children[i];
|
||||
this.totalChars += child.charCount();
|
||||
this.totalLines += child.lineCount();
|
||||
}
|
||||
}
|
||||
|
||||
execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) {
|
||||
if (walkFns.pre) {
|
||||
walkFns.pre(rangeStart, rangeLength, this.children[childIndex], this, nodeType);
|
||||
}
|
||||
if (walkFns.goSubtree) {
|
||||
this.children[childIndex].walk(rangeStart, rangeLength, walkFns);
|
||||
if (walkFns.post) {
|
||||
walkFns.post(rangeStart, rangeLength, this.children[childIndex], this, nodeType);
|
||||
}
|
||||
}
|
||||
else {
|
||||
walkFns.goSubtree = true;
|
||||
}
|
||||
return walkFns.done;
|
||||
}
|
||||
|
||||
skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) {
|
||||
if (walkFns.pre && (!walkFns.done)) {
|
||||
walkFns.pre(relativeStart, relativeLength, this.children[childIndex], this, nodeType);
|
||||
walkFns.goSubtree = true;
|
||||
}
|
||||
}
|
||||
|
||||
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
|
||||
// assume (rangeStart < this.totalChars) && (rangeLength <= this.totalChars)
|
||||
let childIndex = 0;
|
||||
let child = this.children[0];
|
||||
let childCharCount = child.charCount();
|
||||
// find sub-tree containing start
|
||||
let adjustedStart = rangeStart;
|
||||
while (adjustedStart >= childCharCount) {
|
||||
this.skipChild(adjustedStart, rangeLength, childIndex, walkFns, CharRangeSection.PreStart);
|
||||
adjustedStart -= childCharCount;
|
||||
childIndex++;
|
||||
child = this.children[childIndex];
|
||||
childCharCount = child.charCount();
|
||||
}
|
||||
// Case I: both start and end of range in same subtree
|
||||
if ((adjustedStart + rangeLength) <= childCharCount) {
|
||||
if (this.execWalk(adjustedStart, rangeLength, walkFns, childIndex, CharRangeSection.Entire)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Case II: start and end of range in different subtrees (possibly with subtrees in the middle)
|
||||
if (this.execWalk(adjustedStart, childCharCount - adjustedStart, walkFns, childIndex, CharRangeSection.Start)) {
|
||||
return;
|
||||
}
|
||||
let adjustedLength = rangeLength - (childCharCount - adjustedStart);
|
||||
childIndex++;
|
||||
child = this.children[childIndex];
|
||||
childCharCount = child.charCount();
|
||||
while (adjustedLength > childCharCount) {
|
||||
if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) {
|
||||
return;
|
||||
}
|
||||
adjustedLength -= childCharCount;
|
||||
childIndex++;
|
||||
child = this.children[childIndex];
|
||||
childCharCount = child.charCount();
|
||||
}
|
||||
if (adjustedLength > 0) {
|
||||
if (this.execWalk(0, adjustedLength, walkFns, childIndex, CharRangeSection.End)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process any subtrees after the one containing range end
|
||||
if (walkFns.pre) {
|
||||
const clen = this.children.length;
|
||||
if (childIndex < (clen - 1)) {
|
||||
for (let ej = childIndex + 1; ej < clen; ej++) {
|
||||
this.skipChild(0, 0, ej, walkFns, CharRangeSection.PostEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
charOffsetToLineNumberAndPos(lineNumber: number, charOffset: number): ILineInfo {
|
||||
const childInfo = this.childFromCharOffset(lineNumber, charOffset);
|
||||
if (!childInfo.child) {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: charOffset,
|
||||
};
|
||||
}
|
||||
else if (childInfo.childIndex < this.children.length) {
|
||||
if (childInfo.child.isLeaf()) {
|
||||
return {
|
||||
line: childInfo.lineNumber,
|
||||
offset: childInfo.charOffset,
|
||||
text: (<LineLeaf>(childInfo.child)).text,
|
||||
leaf: (<LineLeaf>(childInfo.child))
|
||||
};
|
||||
}
|
||||
else {
|
||||
const lineNode = <LineNode>(childInfo.child);
|
||||
return lineNode.charOffsetToLineNumberAndPos(childInfo.lineNumber, childInfo.charOffset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const lineInfo = this.lineNumberToInfo(this.lineCount(), 0);
|
||||
return { line: this.lineCount(), offset: lineInfo.leaf.charCount() };
|
||||
}
|
||||
}
|
||||
|
||||
lineNumberToInfo(lineNumber: number, charOffset: number): ILineInfo {
|
||||
const childInfo = this.childFromLineNumber(lineNumber, charOffset);
|
||||
if (!childInfo.child) {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: charOffset
|
||||
};
|
||||
}
|
||||
else if (childInfo.child.isLeaf()) {
|
||||
return {
|
||||
line: lineNumber,
|
||||
offset: childInfo.charOffset,
|
||||
text: (<LineLeaf>(childInfo.child)).text,
|
||||
leaf: (<LineLeaf>(childInfo.child))
|
||||
};
|
||||
}
|
||||
else {
|
||||
const lineNode = <LineNode>(childInfo.child);
|
||||
return lineNode.lineNumberToInfo(childInfo.relativeLineNumber, childInfo.charOffset);
|
||||
}
|
||||
}
|
||||
|
||||
childFromLineNumber(lineNumber: number, charOffset: number) {
|
||||
let child: LineCollection;
|
||||
let relativeLineNumber = lineNumber;
|
||||
let i: number;
|
||||
let len: number;
|
||||
for (i = 0, len = this.children.length; i < len; i++) {
|
||||
child = this.children[i];
|
||||
const childLineCount = child.lineCount();
|
||||
if (childLineCount >= relativeLineNumber) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
relativeLineNumber -= childLineCount;
|
||||
charOffset += child.charCount();
|
||||
}
|
||||
}
|
||||
return {
|
||||
child: child,
|
||||
childIndex: i,
|
||||
relativeLineNumber: relativeLineNumber,
|
||||
charOffset: charOffset
|
||||
};
|
||||
}
|
||||
|
||||
childFromCharOffset(lineNumber: number, charOffset: number) {
|
||||
let child: LineCollection;
|
||||
let i: number;
|
||||
let len: number;
|
||||
for (i = 0, len = this.children.length; i < len; i++) {
|
||||
child = this.children[i];
|
||||
if (child.charCount() > charOffset) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
charOffset -= child.charCount();
|
||||
lineNumber += child.lineCount();
|
||||
}
|
||||
}
|
||||
return {
|
||||
child: child,
|
||||
childIndex: i,
|
||||
charOffset: charOffset,
|
||||
lineNumber: lineNumber
|
||||
};
|
||||
}
|
||||
|
||||
splitAfter(childIndex: number) {
|
||||
let splitNode: LineNode;
|
||||
const clen = this.children.length;
|
||||
childIndex++;
|
||||
const endLength = childIndex;
|
||||
if (childIndex < clen) {
|
||||
splitNode = new LineNode();
|
||||
while (childIndex < clen) {
|
||||
splitNode.add(this.children[childIndex]);
|
||||
childIndex++;
|
||||
}
|
||||
splitNode.updateCounts();
|
||||
}
|
||||
this.children.length = endLength;
|
||||
return splitNode;
|
||||
}
|
||||
|
||||
remove(child: LineCollection) {
|
||||
const childIndex = this.findChildIndex(child);
|
||||
const clen = this.children.length;
|
||||
if (childIndex < (clen - 1)) {
|
||||
for (let i = childIndex; i < (clen - 1); i++) {
|
||||
this.children[i] = this.children[i + 1];
|
||||
}
|
||||
}
|
||||
this.children.length--;
|
||||
}
|
||||
|
||||
findChildIndex(child: LineCollection) {
|
||||
let childIndex = 0;
|
||||
const clen = this.children.length;
|
||||
while ((this.children[childIndex] !== child) && (childIndex < clen)) childIndex++;
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
insertAt(child: LineCollection, nodes: LineCollection[]) {
|
||||
let childIndex = this.findChildIndex(child);
|
||||
const clen = this.children.length;
|
||||
const nodeCount = nodes.length;
|
||||
// if child is last and there is more room and only one node to place, place it
|
||||
if ((clen < lineCollectionCapacity) && (childIndex === (clen - 1)) && (nodeCount === 1)) {
|
||||
this.add(nodes[0]);
|
||||
this.updateCounts();
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
const shiftNode = this.splitAfter(childIndex);
|
||||
let nodeIndex = 0;
|
||||
childIndex++;
|
||||
while ((childIndex < lineCollectionCapacity) && (nodeIndex < nodeCount)) {
|
||||
this.children[childIndex] = nodes[nodeIndex];
|
||||
childIndex++;
|
||||
nodeIndex++;
|
||||
}
|
||||
let splitNodes: LineNode[] = [];
|
||||
let splitNodeCount = 0;
|
||||
if (nodeIndex < nodeCount) {
|
||||
splitNodeCount = Math.ceil((nodeCount - nodeIndex) / lineCollectionCapacity);
|
||||
splitNodes = <LineNode[]>new Array(splitNodeCount);
|
||||
let splitNodeIndex = 0;
|
||||
for (let i = 0; i < splitNodeCount; i++) {
|
||||
splitNodes[i] = new LineNode();
|
||||
}
|
||||
let splitNode = <LineNode>splitNodes[0];
|
||||
while (nodeIndex < nodeCount) {
|
||||
splitNode.add(nodes[nodeIndex]);
|
||||
nodeIndex++;
|
||||
if (splitNode.children.length === lineCollectionCapacity) {
|
||||
splitNodeIndex++;
|
||||
splitNode = <LineNode>splitNodes[splitNodeIndex];
|
||||
}
|
||||
}
|
||||
for (let i = splitNodes.length - 1; i >= 0; i--) {
|
||||
if (splitNodes[i].children.length === 0) {
|
||||
splitNodes.length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shiftNode) {
|
||||
splitNodes[splitNodes.length] = shiftNode;
|
||||
}
|
||||
this.updateCounts();
|
||||
for (let i = 0; i < splitNodeCount; i++) {
|
||||
(<LineNode>splitNodes[i]).updateCounts();
|
||||
}
|
||||
return splitNodes;
|
||||
}
|
||||
}
|
||||
|
||||
// assume there is room for the item; return true if more room
|
||||
add(collection: LineCollection) {
|
||||
this.children[this.children.length] = collection;
|
||||
return (this.children.length < lineCollectionCapacity);
|
||||
}
|
||||
|
||||
charCount() {
|
||||
return this.totalChars;
|
||||
}
|
||||
|
||||
lineCount() {
|
||||
return this.totalLines;
|
||||
}
|
||||
}
|
||||
|
||||
export class LineLeaf implements LineCollection {
|
||||
constructor(public text: string) {
|
||||
}
|
||||
|
||||
isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
|
||||
walkFns.leaf(rangeStart, rangeLength, this);
|
||||
}
|
||||
|
||||
charCount() {
|
||||
return this.text.length;
|
||||
}
|
||||
|
||||
lineCount() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
+227
-36
@@ -4,6 +4,48 @@
|
||||
/* tslint:disable:no-null-keyword */
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
const net: {
|
||||
connect(options: { port: number }, onConnect?: () => void): NodeSocket
|
||||
} = require("net");
|
||||
|
||||
const childProcess: {
|
||||
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
|
||||
} = require("child_process");
|
||||
|
||||
const os: {
|
||||
homedir(): string
|
||||
} = require("os");
|
||||
|
||||
|
||||
function getGlobalTypingsCacheLocation() {
|
||||
let basePath: string;
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir();
|
||||
break;
|
||||
case "linux":
|
||||
basePath = os.homedir();
|
||||
break;
|
||||
case "darwin":
|
||||
basePath = combinePaths(os.homedir(), "Library/Application Support/");
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.assert(basePath !== undefined);
|
||||
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
|
||||
}
|
||||
|
||||
interface NodeChildProcess {
|
||||
send(message: any, sendHandle?: any): void;
|
||||
on(message: "message", f: (m: any) => void): void;
|
||||
kill(): void;
|
||||
}
|
||||
|
||||
interface NodeSocket {
|
||||
write(data: string, encoding: string): boolean;
|
||||
}
|
||||
|
||||
interface ReadLineOptions {
|
||||
input: NodeJS.ReadableStream;
|
||||
output?: NodeJS.WritableStream;
|
||||
@@ -46,6 +88,7 @@ namespace ts.server {
|
||||
const readline: {
|
||||
createInterface(options: ReadLineOptions): NodeJS.EventEmitter;
|
||||
} = require("readline");
|
||||
|
||||
const fs: {
|
||||
openSync(path: string, options: string): number;
|
||||
close(fd: number): void;
|
||||
@@ -55,6 +98,7 @@ namespace ts.server {
|
||||
stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void;
|
||||
} = require("fs");
|
||||
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
@@ -62,12 +106,14 @@ namespace ts.server {
|
||||
});
|
||||
|
||||
class Logger implements ts.server.Logger {
|
||||
fd = -1;
|
||||
seq = 0;
|
||||
inGroup = false;
|
||||
firstInGroup = true;
|
||||
private fd = -1;
|
||||
private seq = 0;
|
||||
private inGroup = false;
|
||||
private firstInGroup = true;
|
||||
|
||||
constructor(public logFilename: string, public level: string) {
|
||||
constructor(private readonly logFilename: string,
|
||||
private readonly traceToConsole: boolean,
|
||||
private readonly level: LogLevel) {
|
||||
}
|
||||
|
||||
static padStringRight(str: string, padding: string) {
|
||||
@@ -80,12 +126,16 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
getLogFileName() {
|
||||
return this.logFilename;
|
||||
}
|
||||
|
||||
perftrc(s: string) {
|
||||
this.msg(s, "Perf");
|
||||
this.msg(s, Msg.Perf);
|
||||
}
|
||||
|
||||
info(s: string) {
|
||||
this.msg(s, "Info");
|
||||
this.msg(s, Msg.Info);
|
||||
}
|
||||
|
||||
startGroup() {
|
||||
@@ -100,21 +150,20 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
loggingEnabled() {
|
||||
return !!this.logFilename;
|
||||
return !!this.logFilename || this.traceToConsole;
|
||||
}
|
||||
|
||||
isVerbose() {
|
||||
return this.loggingEnabled() && (this.level == "verbose");
|
||||
hasLevel(level: LogLevel) {
|
||||
return this.loggingEnabled() && this.level >= level;
|
||||
}
|
||||
|
||||
|
||||
msg(s: string, type = "Err") {
|
||||
msg(s: string, type: Msg.Types = Msg.Err) {
|
||||
if (this.fd < 0) {
|
||||
if (this.logFilename) {
|
||||
this.fd = fs.openSync(this.logFilename, "w");
|
||||
}
|
||||
}
|
||||
if (this.fd >= 0) {
|
||||
if (this.fd >= 0 || this.traceToConsole) {
|
||||
s = s + "\n";
|
||||
const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " ");
|
||||
if (this.firstInGroup) {
|
||||
@@ -125,19 +174,112 @@ namespace ts.server {
|
||||
this.seq++;
|
||||
this.firstInGroup = true;
|
||||
}
|
||||
const buf = new Buffer(s);
|
||||
fs.writeSync(this.fd, buf, 0, buf.length, null);
|
||||
if (this.fd >= 0) {
|
||||
const buf = new Buffer(s);
|
||||
fs.writeSync(this.fd, buf, 0, buf.length, null);
|
||||
}
|
||||
if (this.traceToConsole) {
|
||||
console.warn(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NodeTypingsInstaller implements ITypingsInstaller {
|
||||
private installer: NodeChildProcess;
|
||||
private socket: NodeSocket;
|
||||
private projectService: ProjectService;
|
||||
|
||||
constructor(
|
||||
private readonly logger: server.Logger,
|
||||
private readonly eventPort: number,
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
private newLine: string) {
|
||||
if (eventPort) {
|
||||
const s = net.connect({ port: eventPort }, () => {
|
||||
this.socket = s;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attach(projectService: ProjectService) {
|
||||
this.projectService = projectService;
|
||||
if (this.logger.hasLevel(LogLevel.requestTime)) {
|
||||
this.logger.info("Binding...");
|
||||
}
|
||||
|
||||
const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation];
|
||||
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
|
||||
args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
|
||||
}
|
||||
const execArgv: string[] = [];
|
||||
{
|
||||
for (const arg of process.execArgv) {
|
||||
const match = /^--(debug|inspect)(=(\d+))?$/.exec(arg);
|
||||
if (match) {
|
||||
// if port is specified - use port + 1
|
||||
// otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1
|
||||
const currentPort = match[3] !== undefined
|
||||
? +match[3]
|
||||
: match[1] === "debug" ? 5858 : 9229;
|
||||
execArgv.push(`--${match[1]}=${currentPort + 1}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args, { execArgv });
|
||||
this.installer.on("message", m => this.handleMessage(m));
|
||||
process.on("exit", () => {
|
||||
this.installer.kill();
|
||||
});
|
||||
}
|
||||
|
||||
onProjectClosed(p: Project): void {
|
||||
this.installer.send({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void {
|
||||
const request = createInstallTypingsRequest(project, typingOptions);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
||||
}
|
||||
this.installer.send(request);
|
||||
}
|
||||
|
||||
private handleMessage(response: SetTypings | InvalidateCachedTypings) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Received response: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
if (response.kind == "set" && this.socket) {
|
||||
this.socket.write(formatMessage({ seq: 0, type: "event", message: response }, this.logger, Buffer.byteLength, this.newLine), "utf8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IOSession extends Session {
|
||||
constructor(host: ServerHost, logger: ts.server.Logger) {
|
||||
super(host, Buffer.byteLength, process.hrtime, logger);
|
||||
constructor(
|
||||
host: ServerHost,
|
||||
cancellationToken: HostCancellationToken,
|
||||
installerEventPort: number,
|
||||
canUseEvents: boolean,
|
||||
useSingleInferredProject: boolean,
|
||||
globalTypingsCacheLocation: string,
|
||||
logger: server.Logger) {
|
||||
super(
|
||||
host,
|
||||
cancellationToken,
|
||||
useSingleInferredProject,
|
||||
new NodeTypingsInstaller(logger, installerEventPort, globalTypingsCacheLocation, host.newLine),
|
||||
Buffer.byteLength,
|
||||
process.hrtime,
|
||||
logger,
|
||||
canUseEvents);
|
||||
}
|
||||
|
||||
exit() {
|
||||
this.projectService.log("Exiting...", "Info");
|
||||
this.logger.info("Exiting...");
|
||||
this.projectService.closeLog();
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -156,11 +298,13 @@ namespace ts.server {
|
||||
|
||||
interface LogOptions {
|
||||
file?: string;
|
||||
detailLevel?: string;
|
||||
detailLevel?: LogLevel;
|
||||
traceToConsole?: boolean;
|
||||
logToFile?: boolean;
|
||||
}
|
||||
|
||||
function parseLoggingEnvironmentString(logEnvStr: string): LogOptions {
|
||||
const logEnv: LogOptions = {};
|
||||
const logEnv: LogOptions = { logToFile: true };
|
||||
const args = logEnvStr.split(" ");
|
||||
for (let i = 0, len = args.length; i < (len - 1); i += 2) {
|
||||
const option = args[i];
|
||||
@@ -168,10 +312,17 @@ namespace ts.server {
|
||||
if (option && value) {
|
||||
switch (option) {
|
||||
case "-file":
|
||||
logEnv.file = value;
|
||||
logEnv.file = stripQuotes(value);
|
||||
break;
|
||||
case "-level":
|
||||
logEnv.detailLevel = value;
|
||||
const level: LogLevel = (<any>LogLevel)[value];
|
||||
logEnv.detailLevel = typeof level === "number" ? level : LogLevel.normal;
|
||||
break;
|
||||
case "-traceToConsole":
|
||||
logEnv.traceToConsole = value.toLowerCase() === "true";
|
||||
break;
|
||||
case "-logToFile":
|
||||
logEnv.logToFile = value.toLowerCase() === "true";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -182,21 +333,25 @@ namespace ts.server {
|
||||
// TSS_LOG "{ level: "normal | verbose | terse", file?: string}"
|
||||
function createLoggerFromEnv() {
|
||||
let fileName: string = undefined;
|
||||
let detailLevel = "normal";
|
||||
let detailLevel = LogLevel.normal;
|
||||
let traceToConsole = false;
|
||||
const logEnvStr = process.env["TSS_LOG"];
|
||||
if (logEnvStr) {
|
||||
const logEnv = parseLoggingEnvironmentString(logEnvStr);
|
||||
if (logEnv.file) {
|
||||
fileName = logEnv.file;
|
||||
}
|
||||
else {
|
||||
fileName = __dirname + "/.log" + process.pid.toString();
|
||||
if (logEnv.logToFile) {
|
||||
if (logEnv.file) {
|
||||
fileName = logEnv.file;
|
||||
}
|
||||
else {
|
||||
fileName = __dirname + "/.log" + process.pid.toString();
|
||||
}
|
||||
}
|
||||
if (logEnv.detailLevel) {
|
||||
detailLevel = logEnv.detailLevel;
|
||||
}
|
||||
traceToConsole = logEnv.traceToConsole;
|
||||
}
|
||||
return new Logger(fileName, detailLevel);
|
||||
return new Logger(fileName, traceToConsole, detailLevel);
|
||||
}
|
||||
// This places log file in the directory containing editorServices.js
|
||||
// TODO: check that this location is writable
|
||||
@@ -295,15 +450,16 @@ namespace ts.server {
|
||||
const pollingWatchedFileSet = createPollingWatchedFileSet();
|
||||
const logger = createLoggerFromEnv();
|
||||
|
||||
const pending: string[] = [];
|
||||
const pending: Buffer[] = [];
|
||||
let canWrite = true;
|
||||
function writeMessage(s: string) {
|
||||
|
||||
function writeMessage(buf: Buffer) {
|
||||
if (!canWrite) {
|
||||
pending.push(s);
|
||||
pending.push(buf);
|
||||
}
|
||||
else {
|
||||
canWrite = false;
|
||||
process.stdout.write(new Buffer(s, "utf8"), setCanWriteFlagAndWriteMessageIfNecessary);
|
||||
process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +473,7 @@ namespace ts.server {
|
||||
const sys = <ServerHost>ts.sys;
|
||||
|
||||
// Override sys.write because fs.writeSync is not reliable on Node 4
|
||||
sys.write = (s: string) => writeMessage(s);
|
||||
sys.write = (s: string) => writeMessage(new Buffer(s, "utf8"));
|
||||
sys.watchFile = (fileName, callback) => {
|
||||
const watchedFile = pollingWatchedFileSet.addFile(fileName, callback);
|
||||
return {
|
||||
@@ -327,9 +483,44 @@ namespace ts.server {
|
||||
|
||||
sys.setTimeout = setTimeout;
|
||||
sys.clearTimeout = clearTimeout;
|
||||
sys.setImmediate = setImmediate;
|
||||
sys.clearImmediate = clearImmediate;
|
||||
if (typeof global !== "undefined" && global.gc) {
|
||||
sys.gc = () => global.gc();
|
||||
}
|
||||
|
||||
const ioSession = new IOSession(sys, logger);
|
||||
process.on("uncaughtException", function(err: Error) {
|
||||
let cancellationToken: HostCancellationToken;
|
||||
try {
|
||||
const factory = require("./cancellationToken");
|
||||
cancellationToken = factory(sys.args);
|
||||
}
|
||||
catch (e) {
|
||||
cancellationToken = {
|
||||
isCancellationRequested: () => false
|
||||
};
|
||||
};
|
||||
|
||||
let eventPort: number;
|
||||
{
|
||||
const index = sys.args.indexOf("--eventPort");
|
||||
if (index >= 0 && index < sys.args.length - 1) {
|
||||
const v = parseInt(sys.args[index + 1]);
|
||||
if (!isNaN(v)) {
|
||||
eventPort = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
|
||||
const ioSession = new IOSession(
|
||||
sys,
|
||||
cancellationToken,
|
||||
eventPort,
|
||||
/*canUseEvents*/ eventPort === undefined,
|
||||
useSingleInferredProject,
|
||||
getGlobalTypingsCacheLocation(),
|
||||
logger);
|
||||
process.on("uncaughtException", function (err: Error) {
|
||||
ioSession.logError(err, "unknown");
|
||||
});
|
||||
// Start listening
|
||||
|
||||
+930
-644
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"pretty": true,
|
||||
"out": "../../built/local/tsserver.js",
|
||||
"outFile": "../../built/local/tsserver.js",
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"types": [
|
||||
@@ -15,6 +15,12 @@
|
||||
"files": [
|
||||
"../services/shims.ts",
|
||||
"../services/utilities.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
"lshost.ts",
|
||||
"typingsCache.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"session.ts",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"out": "../../built/local/tsserverlibrary.js",
|
||||
"outFile": "../../built/local/tsserverlibrary.js",
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"declaration": true,
|
||||
@@ -12,6 +12,12 @@
|
||||
"files": [
|
||||
"../services/shims.ts",
|
||||
"../services/utilities.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
"lshost.ts",
|
||||
"typingsCache.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"session.ts"
|
||||
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
/// <reference path="../compiler/types.ts"/>
|
||||
/// <reference path="../compiler/sys.ts"/>
|
||||
/// <reference path="../services/jsTyping.ts"/>
|
||||
|
||||
declare namespace ts.server {
|
||||
export interface CompressedData {
|
||||
length: number;
|
||||
compressionKind: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface ServerHost extends System {
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
|
||||
clearTimeout(timeoutId: any): void;
|
||||
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;
|
||||
clearImmediate(timeoutId: any): void;
|
||||
gc?(): void;
|
||||
trace?(s: string): void;
|
||||
}
|
||||
|
||||
export interface TypingInstallerRequest {
|
||||
readonly projectName: string;
|
||||
readonly kind: "discover" | "closeProject";
|
||||
}
|
||||
|
||||
export interface DiscoverTypings extends TypingInstallerRequest {
|
||||
readonly fileNames: string[];
|
||||
readonly projectRootPath: ts.Path;
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly cachePath?: string;
|
||||
readonly kind: "discover";
|
||||
}
|
||||
|
||||
export interface CloseProject extends TypingInstallerRequest {
|
||||
readonly kind: "closeProject";
|
||||
}
|
||||
|
||||
export interface TypingInstallerResponse {
|
||||
readonly projectName: string;
|
||||
readonly kind: "set" | "invalidate";
|
||||
}
|
||||
|
||||
export interface SetTypings extends TypingInstallerResponse {
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typings: string[];
|
||||
readonly kind: "set";
|
||||
}
|
||||
|
||||
export interface InvalidateCachedTypings extends TypingInstallerResponse {
|
||||
readonly kind: "invalidate";
|
||||
}
|
||||
|
||||
export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
|
||||
writeFile(path: string, content: string): void;
|
||||
createDirectory(path: string): void;
|
||||
watchFile?(path: string, callback: FileWatcherCallback): FileWatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/// <reference path="project.ts"/>
|
||||
|
||||
namespace ts.server {
|
||||
export interface ITypingsInstaller {
|
||||
enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void;
|
||||
attach(projectService: ProjectService): void;
|
||||
onProjectClosed(p: Project): void;
|
||||
readonly globalTypingsCacheLocation: string;
|
||||
}
|
||||
|
||||
export const nullTypingsInstaller: ITypingsInstaller = {
|
||||
enqueueInstallTypingsRequest: () => {},
|
||||
attach: (projectService: ProjectService) => {},
|
||||
onProjectClosed: (p: Project) => {},
|
||||
globalTypingsCacheLocation: undefined
|
||||
};
|
||||
|
||||
class TypingsCacheEntry {
|
||||
readonly typingOptions: TypingOptions;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly typings: TypingsArray;
|
||||
poisoned: boolean;
|
||||
}
|
||||
|
||||
function setIsEqualTo(arr1: string[], arr2: string[]): boolean {
|
||||
if (arr1 === arr2) {
|
||||
return true;
|
||||
}
|
||||
if ((arr1 || emptyArray).length === 0 && (arr2 || emptyArray).length === 0) {
|
||||
return true;
|
||||
}
|
||||
const set: Map<boolean> = createMap<boolean>();
|
||||
let unique = 0;
|
||||
|
||||
for (const v of arr1) {
|
||||
if (set[v] !== true) {
|
||||
set[v] = true;
|
||||
unique++;
|
||||
}
|
||||
}
|
||||
for (const v of arr2) {
|
||||
if (!hasProperty(set, v)) {
|
||||
return false;
|
||||
}
|
||||
if (set[v] === true) {
|
||||
set[v] = false;
|
||||
unique--;
|
||||
}
|
||||
}
|
||||
return unique === 0;
|
||||
}
|
||||
|
||||
function typingOptionsChanged(opt1: TypingOptions, opt2: TypingOptions): boolean {
|
||||
return opt1.enableAutoDiscovery !== opt2.enableAutoDiscovery ||
|
||||
!setIsEqualTo(opt1.include, opt2.include) ||
|
||||
!setIsEqualTo(opt1.exclude, opt2.exclude);
|
||||
}
|
||||
|
||||
function compilerOptionsChanged(opt1: CompilerOptions, opt2: CompilerOptions): boolean {
|
||||
// TODO: add more relevant properties
|
||||
return opt1.allowJs != opt2.allowJs;
|
||||
}
|
||||
|
||||
export interface TypingsArray extends ReadonlyArray<string> {
|
||||
" __typingsArrayBrand": any;
|
||||
}
|
||||
|
||||
function toTypingsArray(arr: string[]): TypingsArray {
|
||||
arr.sort();
|
||||
return <any>arr;
|
||||
}
|
||||
|
||||
export class TypingsCache {
|
||||
private readonly perProjectCache: Map<TypingsCacheEntry> = createMap<TypingsCacheEntry>();
|
||||
|
||||
constructor(private readonly installer: ITypingsInstaller) {
|
||||
}
|
||||
|
||||
getTypingsForProject(project: Project, forceRefresh: boolean): TypingsArray {
|
||||
const typingOptions = project.getTypingOptions();
|
||||
|
||||
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
|
||||
return <any>emptyArray;
|
||||
}
|
||||
|
||||
const entry = this.perProjectCache[project.getProjectName()];
|
||||
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
|
||||
if (forceRefresh || !entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
|
||||
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
|
||||
// instead it acts as a placeholder to prevent issuing multiple requests
|
||||
this.perProjectCache[project.getProjectName()] = {
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
typings: result,
|
||||
poisoned: true
|
||||
};
|
||||
// something has been changed, issue a request to update typings
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
invalidateCachedTypingsForProject(project: Project) {
|
||||
const typingOptions = project.getTypingOptions();
|
||||
if (!typingOptions.enableAutoDiscovery) {
|
||||
return;
|
||||
}
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
}
|
||||
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typingOptions: TypingOptions, newTypings: string[]) {
|
||||
this.perProjectCache[projectName] = {
|
||||
compilerOptions,
|
||||
typingOptions,
|
||||
typings: toTypingsArray(newTypings),
|
||||
poisoned: false
|
||||
};
|
||||
}
|
||||
|
||||
onProjectClosed(project: Project) {
|
||||
delete this.perProjectCache[project.getProjectName()];
|
||||
this.installer.onProjectClosed(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/// <reference path="typingsInstaller.ts"/>
|
||||
/// <reference types="node" />
|
||||
|
||||
namespace ts.server.typingsInstaller {
|
||||
const fs: {
|
||||
appendFileSync(file: string, content: string): void
|
||||
} = require("fs");
|
||||
|
||||
const path: {
|
||||
join(...parts: string[]): string;
|
||||
dirname(path: string): string;
|
||||
basename(path: string, extension?: string): string;
|
||||
} = require("path");
|
||||
|
||||
class FileLog implements Log {
|
||||
constructor(private readonly logFile?: string) {
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.logFile !== undefined;
|
||||
}
|
||||
writeLine(text: string) {
|
||||
fs.appendFileSync(this.logFile, text + sys.newLine);
|
||||
}
|
||||
}
|
||||
|
||||
function getNPMLocation(processName: string) {
|
||||
if (path.basename(processName).indexOf("node") == 0) {
|
||||
return `"${path.join(path.dirname(process.argv[0]), "npm")}"`;
|
||||
}
|
||||
else {
|
||||
return "npm";
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeTypingsInstaller extends TypingsInstaller {
|
||||
private readonly exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
|
||||
|
||||
readonly installTypingHost: InstallTypingHost = sys;
|
||||
|
||||
constructor(globalTypingsCacheLocation: string, throttleLimit: number, log: Log) {
|
||||
super(
|
||||
globalTypingsCacheLocation,
|
||||
/*npmPath*/ getNPMLocation(process.argv[0]),
|
||||
toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)),
|
||||
throttleLimit,
|
||||
log);
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Process id: ${process.pid}`);
|
||||
}
|
||||
const { exec } = require("child_process");
|
||||
this.exec = exec;
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
process.on("message", (req: DiscoverTypings | CloseProject) => {
|
||||
switch (req.kind) {
|
||||
case "discover":
|
||||
this.install(req);
|
||||
break;
|
||||
case "closeProject":
|
||||
this.closeProject(req);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected sendResponse(response: SetTypings | InvalidateCachedTypings) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Sending response: ${JSON.stringify(response)}`);
|
||||
}
|
||||
process.send(response);
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Response has been sent.`);
|
||||
}
|
||||
}
|
||||
|
||||
protected runCommand(requestKind: RequestKind, requestId: number, command: string, cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`#${requestId} running command '${command}'.`);
|
||||
}
|
||||
this.exec(command, { cwd }, (err, stdout, stderr) => {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`${requestKind} #${requestId} stdout: ${stdout}`);
|
||||
this.log.writeLine(`${requestKind} #${requestId} stderr: ${stderr}`);
|
||||
}
|
||||
onRequestCompleted(err, stdout, stderr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findArgument(argumentName: string) {
|
||||
const index = sys.args.indexOf(argumentName);
|
||||
return index >= 0 && index < sys.args.length - 1
|
||||
? sys.args[index + 1]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
const logFilePath = findArgument("--logFile");
|
||||
const globalTypingsCacheLocation = findArgument("--globalTypingsCacheLocation");
|
||||
const log = new FileLog(logFilePath);
|
||||
if (log.isEnabled()) {
|
||||
process.on("uncaughtException", (e: Error) => {
|
||||
log.writeLine(`Unhandled exception: ${e} at ${e.stack}`);
|
||||
});
|
||||
}
|
||||
process.on("disconnect", () => {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Parent process has exited, shutting down...`);
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, /*throttleLimit*/5, log);
|
||||
installer.init();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"pretty": true,
|
||||
"outFile": "../../../built/local/typingsInstaller.js",
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"../types.d.ts",
|
||||
"typingsInstaller.ts",
|
||||
"nodeTypingsInstaller.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
/// <reference path="../../compiler/core.ts" />
|
||||
/// <reference path="../../compiler/moduleNameResolver.ts" />
|
||||
/// <reference path="../../services/jsTyping.ts"/>
|
||||
/// <reference path="../types.d.ts"/>
|
||||
|
||||
namespace ts.server.typingsInstaller {
|
||||
interface NpmConfig {
|
||||
devDependencies: MapLike<any>;
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
isEnabled(): boolean;
|
||||
writeLine(text: string): void;
|
||||
}
|
||||
|
||||
const nullLog: Log = {
|
||||
isEnabled: () => false,
|
||||
writeLine: () => {}
|
||||
};
|
||||
|
||||
function typingToFileName(cachePath: string, packageName: string, installTypingHost: InstallTypingHost): string {
|
||||
const result = resolveModuleName(packageName, combinePaths(cachePath, "index.d.ts"), { moduleResolution: ModuleResolutionKind.NodeJs }, installTypingHost);
|
||||
return result.resolvedModule && result.resolvedModule.resolvedFileName;
|
||||
}
|
||||
|
||||
export enum PackageNameValidationResult {
|
||||
Ok,
|
||||
ScopedPackagesNotSupported,
|
||||
NameTooLong,
|
||||
NameStartsWithDot,
|
||||
NameStartsWithUnderscore,
|
||||
NameContainsNonURISafeCharacters
|
||||
}
|
||||
|
||||
|
||||
export const MaxPackageNameLength = 214;
|
||||
/**
|
||||
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
|
||||
*/
|
||||
export function validatePackageName(packageName: string): PackageNameValidationResult {
|
||||
Debug.assert(!!packageName, "Package name is not specified");
|
||||
if (packageName.length > MaxPackageNameLength) {
|
||||
return PackageNameValidationResult.NameTooLong;
|
||||
}
|
||||
if (packageName.charCodeAt(0) === CharacterCodes.dot) {
|
||||
return PackageNameValidationResult.NameStartsWithDot;
|
||||
}
|
||||
if (packageName.charCodeAt(0) === CharacterCodes._) {
|
||||
return PackageNameValidationResult.NameStartsWithUnderscore;
|
||||
}
|
||||
// check if name is scope package like: starts with @ and has one '/' in the middle
|
||||
// scoped packages are not currently supported
|
||||
// TODO: when support will be added we'll need to split and check both scope and package name
|
||||
if (/^@[^/]+\/[^/]+$/.test(packageName)) {
|
||||
return PackageNameValidationResult.ScopedPackagesNotSupported;
|
||||
}
|
||||
if (encodeURIComponent(packageName) !== packageName) {
|
||||
return PackageNameValidationResult.NameContainsNonURISafeCharacters;
|
||||
}
|
||||
return PackageNameValidationResult.Ok;
|
||||
}
|
||||
|
||||
export const NpmViewRequest: "npm view" = "npm view";
|
||||
export const NpmInstallRequest: "npm install" = "npm install";
|
||||
|
||||
export type RequestKind = typeof NpmViewRequest | typeof NpmInstallRequest;
|
||||
|
||||
export type RequestCompletedAction = (err: Error, stdout: string, stderr: string) => void;
|
||||
type PendingRequest = {
|
||||
requestKind: RequestKind;
|
||||
requestId: number;
|
||||
command: string;
|
||||
cwd: string;
|
||||
onRequestCompleted: RequestCompletedAction
|
||||
};
|
||||
|
||||
export abstract class TypingsInstaller {
|
||||
private readonly packageNameToTypingLocation: Map<string> = createMap<string>();
|
||||
private readonly missingTypingsSet: Map<true> = createMap<true>();
|
||||
private readonly knownCachesSet: Map<true> = createMap<true>();
|
||||
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
|
||||
readonly pendingRunRequests: PendingRequest[] = [];
|
||||
|
||||
private installRunCount = 1;
|
||||
private inFlightRequestCount = 0;
|
||||
|
||||
abstract readonly installTypingHost: InstallTypingHost;
|
||||
|
||||
constructor(
|
||||
readonly globalCachePath: string,
|
||||
readonly npmPath: string,
|
||||
readonly safeListPath: Path,
|
||||
readonly throttleLimit: number,
|
||||
protected readonly log = nullLog) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.processCacheLocation(this.globalCachePath);
|
||||
}
|
||||
|
||||
closeProject(req: CloseProject) {
|
||||
this.closeWatchers(req.projectName);
|
||||
}
|
||||
|
||||
private closeWatchers(projectName: string): void {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Closing file watchers for project '${projectName}'`);
|
||||
}
|
||||
const watchers = this.projectWatchers[projectName];
|
||||
if (!watchers) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`No watchers are registered for project '${projectName}'`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const w of watchers) {
|
||||
w.close();
|
||||
}
|
||||
|
||||
delete this.projectWatchers[projectName];
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Closing file watchers for project '${projectName}' - done.`);
|
||||
}
|
||||
}
|
||||
|
||||
install(req: DiscoverTypings) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
|
||||
}
|
||||
|
||||
// load existing typing information from the cache
|
||||
if (req.cachePath) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`);
|
||||
}
|
||||
this.processCacheLocation(req.cachePath);
|
||||
}
|
||||
|
||||
const discoverTypingsResult = JsTyping.discoverTypings(
|
||||
this.installTypingHost,
|
||||
req.fileNames,
|
||||
req.projectRootPath,
|
||||
this.safeListPath,
|
||||
this.packageNameToTypingLocation,
|
||||
req.typingOptions,
|
||||
req.compilerOptions);
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`);
|
||||
}
|
||||
|
||||
// respond with whatever cached typings we have now
|
||||
this.sendResponse(this.createSetTypings(req, discoverTypingsResult.cachedTypingPaths));
|
||||
|
||||
// start watching files
|
||||
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch);
|
||||
|
||||
// install typings
|
||||
if (discoverTypingsResult.newTypingNames.length) {
|
||||
this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames);
|
||||
}
|
||||
else {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`No new typings were requested as a result of typings discovery`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processCacheLocation(cacheLocation: string) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Processing cache location '${cacheLocation}'`);
|
||||
}
|
||||
if (this.knownCachesSet[cacheLocation]) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Cache location was already processed...`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const packageJson = combinePaths(cacheLocation, "package.json");
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Trying to find '${packageJson}'...`);
|
||||
}
|
||||
if (this.installTypingHost.fileExists(packageJson)) {
|
||||
const npmConfig = <NpmConfig>JSON.parse(this.installTypingHost.readFile(packageJson));
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Loaded content of '${packageJson}': ${JSON.stringify(npmConfig)}`);
|
||||
}
|
||||
if (npmConfig.devDependencies) {
|
||||
for (const key in npmConfig.devDependencies) {
|
||||
// key is @types/<package name>
|
||||
const packageName = getBaseFileName(key);
|
||||
if (!packageName) {
|
||||
continue;
|
||||
}
|
||||
const typingFile = typingToFileName(cacheLocation, packageName, this.installTypingHost);
|
||||
if (!typingFile) {
|
||||
continue;
|
||||
}
|
||||
const existingTypingFile = this.packageNameToTypingLocation[packageName];
|
||||
if (existingTypingFile === typingFile) {
|
||||
continue;
|
||||
}
|
||||
if (existingTypingFile) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`New typing for package ${packageName} from '${typingFile}' conflicts with existing typing file '${existingTypingFile}'`);
|
||||
}
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`);
|
||||
}
|
||||
this.packageNameToTypingLocation[packageName] = typingFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Finished processing cache location '${cacheLocation}'`);
|
||||
}
|
||||
this.knownCachesSet[cacheLocation] = true;
|
||||
}
|
||||
|
||||
private filterTypings(typingsToInstall: string[]) {
|
||||
if (typingsToInstall.length === 0) {
|
||||
return typingsToInstall;
|
||||
}
|
||||
const result: string[] = [];
|
||||
for (const typing of typingsToInstall) {
|
||||
if (this.missingTypingsSet[typing]) {
|
||||
continue;
|
||||
}
|
||||
const validationResult = validatePackageName(typing);
|
||||
if (validationResult === PackageNameValidationResult.Ok) {
|
||||
result.push(typing);
|
||||
}
|
||||
else {
|
||||
// add typing name to missing set so we won't process it again
|
||||
this.missingTypingsSet[typing] = true;
|
||||
if (this.log.isEnabled()) {
|
||||
switch (validationResult) {
|
||||
case PackageNameValidationResult.NameTooLong:
|
||||
this.log.writeLine(`Package name '${typing}' should be less than ${MaxPackageNameLength} characters`);
|
||||
break;
|
||||
case PackageNameValidationResult.NameStartsWithDot:
|
||||
this.log.writeLine(`Package name '${typing}' cannot start with '.'`);
|
||||
break;
|
||||
case PackageNameValidationResult.NameStartsWithUnderscore:
|
||||
this.log.writeLine(`Package name '${typing}' cannot start with '_'`);
|
||||
break;
|
||||
case PackageNameValidationResult.ScopedPackagesNotSupported:
|
||||
this.log.writeLine(`Package '${typing}' is scoped and currently is not supported`);
|
||||
break;
|
||||
case PackageNameValidationResult.NameContainsNonURISafeCharacters:
|
||||
this.log.writeLine(`Package name '${typing}' contains non URI safe characters`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`);
|
||||
}
|
||||
typingsToInstall = this.filterTypings(typingsToInstall);
|
||||
if (typingsToInstall.length === 0) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`All typings are known to be missing or invalid - no need to go any further`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const npmConfigPath = combinePaths(cachePath, "package.json");
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Npm config file: ${npmConfigPath}`);
|
||||
}
|
||||
if (!this.installTypingHost.fileExists(npmConfigPath)) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Npm config file: '${npmConfigPath}' is missing, creating new one...`);
|
||||
}
|
||||
this.ensureDirectoryExists(cachePath, this.installTypingHost);
|
||||
this.installTypingHost.writeFile(npmConfigPath, "{}");
|
||||
}
|
||||
|
||||
this.runInstall(cachePath, typingsToInstall, installedTypings => {
|
||||
// TODO: watch project directory
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`);
|
||||
}
|
||||
const installedPackages: Map<true> = createMap<true>();
|
||||
const installedTypingFiles: string[] = [];
|
||||
for (const t of installedTypings) {
|
||||
const packageName = getBaseFileName(t);
|
||||
if (!packageName) {
|
||||
continue;
|
||||
}
|
||||
installedPackages[packageName] = true;
|
||||
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost);
|
||||
if (!typingFile) {
|
||||
continue;
|
||||
}
|
||||
if (!this.packageNameToTypingLocation[packageName]) {
|
||||
this.packageNameToTypingLocation[packageName] = typingFile;
|
||||
}
|
||||
installedTypingFiles.push(typingFile);
|
||||
}
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
|
||||
}
|
||||
for (const toInstall of typingsToInstall) {
|
||||
if (!installedPackages[toInstall]) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`New missing typing package '${toInstall}'`);
|
||||
}
|
||||
this.missingTypingsSet[toInstall] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
|
||||
});
|
||||
}
|
||||
|
||||
private runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
|
||||
const requestId = this.installRunCount;
|
||||
|
||||
this.installRunCount++;
|
||||
let execInstallCmdCount = 0;
|
||||
const filteredTypings: string[] = [];
|
||||
for (const typing of typingsToInstall) {
|
||||
execNpmViewTyping(this, typing);
|
||||
}
|
||||
|
||||
function execNpmViewTyping(self: TypingsInstaller, typing: string) {
|
||||
const command = `${self.npmPath} view @types/${typing} --silent name`;
|
||||
self.execAsync(NpmViewRequest, requestId, command, cachePath, (err, stdout, stderr) => {
|
||||
if (stdout) {
|
||||
filteredTypings.push(typing);
|
||||
}
|
||||
execInstallCmdCount++;
|
||||
if (execInstallCmdCount === typingsToInstall.length) {
|
||||
installFilteredTypings(self, filteredTypings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installFilteredTypings(self: TypingsInstaller, filteredTypings: string[]) {
|
||||
if (filteredTypings.length === 0) {
|
||||
postInstallAction([]);
|
||||
return;
|
||||
}
|
||||
const scopedTypings = filteredTypings.map(t => "@types/" + t);
|
||||
const command = `${self.npmPath} install ${scopedTypings.join(" ")} --save-dev`;
|
||||
self.execAsync(NpmInstallRequest, requestId, command, cachePath, (err, stdout, stderr) => {
|
||||
postInstallAction(stdout ? scopedTypings : []);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ensureDirectoryExists(directory: string, host: InstallTypingHost): void {
|
||||
const directoryName = getDirectoryPath(directory);
|
||||
if (!host.directoryExists(directoryName)) {
|
||||
this.ensureDirectoryExists(directoryName, host);
|
||||
}
|
||||
if (!host.directoryExists(directory)) {
|
||||
host.createDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
private watchFiles(projectName: string, files: string[]) {
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
// shut down existing watchers
|
||||
this.closeWatchers(projectName);
|
||||
|
||||
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
|
||||
let isInvoked = false;
|
||||
const watchers: FileWatcher[] = [];
|
||||
for (const file of files) {
|
||||
const w = this.installTypingHost.watchFile(file, f => {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`);
|
||||
}
|
||||
this.sendResponse({ projectName: projectName, kind: "invalidate" });
|
||||
isInvoked = true;
|
||||
});
|
||||
watchers.push(w);
|
||||
}
|
||||
this.projectWatchers[projectName] = watchers;
|
||||
}
|
||||
|
||||
private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {
|
||||
return {
|
||||
projectName: request.projectName,
|
||||
typingOptions: request.typingOptions,
|
||||
compilerOptions: request.compilerOptions,
|
||||
typings,
|
||||
kind: "set"
|
||||
};
|
||||
}
|
||||
|
||||
private execAsync(requestKind: RequestKind, requestId: number, command: string, cwd: string, onRequestCompleted: RequestCompletedAction): void {
|
||||
this.pendingRunRequests.unshift({ requestKind, requestId, command, cwd, onRequestCompleted });
|
||||
this.executeWithThrottling();
|
||||
}
|
||||
|
||||
private executeWithThrottling() {
|
||||
while (this.inFlightRequestCount < this.throttleLimit && this.pendingRunRequests.length) {
|
||||
this.inFlightRequestCount++;
|
||||
const request = this.pendingRunRequests.pop();
|
||||
this.runCommand(request.requestKind, request.requestId, request.command, request.cwd, (err, stdout, stderr) => {
|
||||
this.inFlightRequestCount--;
|
||||
request.onRequestCompleted(err, stdout, stderr);
|
||||
this.executeWithThrottling();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract runCommand(requestKind: RequestKind, requestId: number, command: string, cwd: string, onRequestCompleted: RequestCompletedAction): void;
|
||||
protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
/// <reference path="types.d.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export enum LogLevel {
|
||||
terse,
|
||||
normal,
|
||||
requestTime,
|
||||
verbose
|
||||
}
|
||||
|
||||
export const emptyArray: ReadonlyArray<any> = [];
|
||||
|
||||
export interface Logger {
|
||||
close(): void;
|
||||
hasLevel(level: LogLevel): boolean;
|
||||
loggingEnabled(): boolean;
|
||||
perftrc(s: string): void;
|
||||
info(s: string): void;
|
||||
startGroup(): void;
|
||||
endGroup(): void;
|
||||
msg(s: string, type?: Msg.Types): void;
|
||||
getLogFileName(): string;
|
||||
}
|
||||
|
||||
export namespace Msg {
|
||||
export type Err = "Err";
|
||||
export const Err: Err = "Err";
|
||||
export type Info = "Info";
|
||||
export const Info: Info = "Info";
|
||||
export type Perf = "Perf";
|
||||
export const Perf: Perf = "Perf";
|
||||
export type Types = Err | Info | Perf;
|
||||
}
|
||||
|
||||
function getProjectRootPath(project: Project): Path {
|
||||
switch (project.projectKind) {
|
||||
case ProjectKind.Configured:
|
||||
return <Path>getDirectoryPath(project.getProjectName());
|
||||
case ProjectKind.Inferred:
|
||||
// TODO: fixme
|
||||
return <Path>"";
|
||||
case ProjectKind.External:
|
||||
const projectName = normalizeSlashes(project.getProjectName());
|
||||
return project.projectService.host.fileExists(projectName) ? <Path>getDirectoryPath(projectName) : <Path>projectName;
|
||||
}
|
||||
}
|
||||
|
||||
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings {
|
||||
return {
|
||||
projectName: project.getProjectName(),
|
||||
fileNames: project.getFileNames(),
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
projectRootPath: getProjectRootPath(project),
|
||||
cachePath,
|
||||
kind: "discover"
|
||||
};
|
||||
}
|
||||
|
||||
export namespace Errors {
|
||||
export function ThrowNoProject(): never {
|
||||
throw new Error("No Project.");
|
||||
}
|
||||
export function ThrowProjectLanguageServiceDisabled(): never {
|
||||
throw new Error("The project's language service is disabled.");
|
||||
}
|
||||
export function ThrowProjectDoesNotContainDocument(fileName: string, project: Project): never {
|
||||
throw new Error(`Project '${project.getProjectName()}' does not contain document '${fileName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultFormatCodeSettings(host: ServerHost): FormatCodeSettings {
|
||||
return {
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
newLineCharacter: host.newLine || "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: ts.IndentStyle.Smart,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeMaps(target: MapLike<any>, source: MapLike <any>): void {
|
||||
for (const key in source) {
|
||||
if (hasProperty(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeItemFromSet<T>(items: T[], itemToRemove: T) {
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
const index = items.indexOf(itemToRemove);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
if (index === items.length - 1) {
|
||||
// last item - pop it
|
||||
items.pop();
|
||||
}
|
||||
else {
|
||||
// non-last item - replace it with the last one
|
||||
items[index] = items.pop();
|
||||
}
|
||||
}
|
||||
|
||||
export type NormalizedPath = string & { __normalizedPathTag: any };
|
||||
|
||||
export function toNormalizedPath(fileName: string): NormalizedPath {
|
||||
return <NormalizedPath>normalizePath(fileName);
|
||||
}
|
||||
|
||||
export function normalizedPathToPath(normalizedPath: NormalizedPath, currentDirectory: string, getCanonicalFileName: (f: string) => string): Path {
|
||||
const f = isRootedDiskPath(normalizedPath) ? normalizedPath : getNormalizedAbsolutePath(normalizedPath, currentDirectory);
|
||||
return <Path>getCanonicalFileName(f);
|
||||
}
|
||||
|
||||
export function asNormalizedPath(fileName: string): NormalizedPath {
|
||||
return <NormalizedPath>fileName;
|
||||
}
|
||||
|
||||
export interface NormalizedPathMap<T> {
|
||||
get(path: NormalizedPath): T;
|
||||
set(path: NormalizedPath, value: T): void;
|
||||
contains(path: NormalizedPath): boolean;
|
||||
remove(path: NormalizedPath): void;
|
||||
}
|
||||
|
||||
export function createNormalizedPathMap<T>(): NormalizedPathMap<T> {
|
||||
/* tslint:disable:no-null-keyword */
|
||||
const map: Map<T> = Object.create(null);
|
||||
/* tslint:enable:no-null-keyword */
|
||||
return {
|
||||
get(path) {
|
||||
return map[path];
|
||||
},
|
||||
set(path, value) {
|
||||
map[path] = value;
|
||||
},
|
||||
contains(path) {
|
||||
return hasProperty(map, path);
|
||||
},
|
||||
remove(path) {
|
||||
delete map[path];
|
||||
}
|
||||
};
|
||||
}
|
||||
function throwLanguageServiceIsDisabledError() {
|
||||
throw new Error("LanguageService is disabled");
|
||||
}
|
||||
|
||||
export const nullLanguageService: LanguageService = {
|
||||
cleanupSemanticCache: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSyntacticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSemanticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompilerOptionsDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getEncodedSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getEncodedSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompletionsAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
findReferences: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompletionEntryDetails: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getQuickInfoAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
findRenameLocations: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNameOrDottedNameSpan: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getBreakpointStatementAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getBraceMatchingAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSignatureHelpItems: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getRenameInfo: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getTypeDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getReferencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getDocumentHighlights: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getOccurrencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNavigateToItems: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNavigationBarItems: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getOutliningSpans: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getTodoComments: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getIndentationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getFormattingEditsForRange: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getFormattingEditsForDocument: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getFormattingEditsAfterKeystroke: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getDocCommentTemplateAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
isValidBraceCompletionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getEmitOutput: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getProgram: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNonBoundSourceFile: (): any => throwLanguageServiceIsDisabledError(),
|
||||
dispose: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompletionEntrySymbol: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getImplementationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSourceFile: (): any => throwLanguageServiceIsDisabledError()
|
||||
};
|
||||
|
||||
export interface ServerLanguageServiceHost {
|
||||
setCompilationSettings(options: CompilerOptions): void;
|
||||
notifyFileRemoved(info: ScriptInfo): void;
|
||||
}
|
||||
|
||||
export const nullLanguageServiceHost: ServerLanguageServiceHost = {
|
||||
setCompilationSettings: () => undefined,
|
||||
notifyFileRemoved: () => undefined
|
||||
};
|
||||
|
||||
export interface ProjectOptions {
|
||||
/**
|
||||
* true if config file explicitly listed files
|
||||
**/
|
||||
configHasFilesProperty?: boolean;
|
||||
/**
|
||||
* these fields can be present in the project file
|
||||
**/
|
||||
files?: string[];
|
||||
wildcardDirectories?: Map<WatchDirectoryFlags>;
|
||||
compilerOptions?: CompilerOptions;
|
||||
typingOptions?: TypingOptions;
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
export function isInferredProjectName(name: string) {
|
||||
// POSIX defines /dev/null as a device - there should be no file with this prefix
|
||||
return /dev\/null\/inferredProject\d+\*/.test(name);
|
||||
}
|
||||
|
||||
export function makeInferredProjectName(counter: number) {
|
||||
return `/dev/null/inferredProject${counter}*`;
|
||||
}
|
||||
|
||||
export class ThrottledOperations {
|
||||
private pendingTimeouts: Map<any> = createMap<any>();
|
||||
constructor(private readonly host: ServerHost) {
|
||||
}
|
||||
|
||||
public schedule(operationId: string, delay: number, cb: () => void) {
|
||||
if (hasProperty(this.pendingTimeouts, operationId)) {
|
||||
// another operation was already scheduled for this id - cancel it
|
||||
this.host.clearTimeout(this.pendingTimeouts[operationId]);
|
||||
}
|
||||
// schedule new operation, pass arguments
|
||||
this.pendingTimeouts[operationId] = this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb);
|
||||
}
|
||||
|
||||
private static run(self: ThrottledOperations, operationId: string, cb: () => void) {
|
||||
delete self.pendingTimeouts[operationId];
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
export class GcTimer {
|
||||
private timerId: any;
|
||||
constructor(private readonly host: ServerHost, private readonly delay: number, private readonly logger: Logger) {
|
||||
}
|
||||
|
||||
public scheduleCollect() {
|
||||
if (!this.host.gc || this.timerId != undefined) {
|
||||
// no global.gc or collection was already scheduled - skip this request
|
||||
return;
|
||||
}
|
||||
this.timerId = this.host.setTimeout(GcTimer.run, this.delay, this);
|
||||
}
|
||||
|
||||
private static run(self: GcTimer) {
|
||||
self.timerId = undefined;
|
||||
|
||||
const log = self.logger.hasLevel(LogLevel.requestTime);
|
||||
const before = log && self.host.getMemoryUsage();
|
||||
|
||||
self.host.gc();
|
||||
if (log) {
|
||||
const after = self.host.getMemoryUsage();
|
||||
self.logger.perftrc(`GC::before ${before}, after ${after}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace ts.formatting {
|
||||
delta: number;
|
||||
}
|
||||
|
||||
export function formatOnEnter(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {
|
||||
export function formatOnEnter(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] {
|
||||
const line = sourceFile.getLineAndCharacterOfPosition(position).line;
|
||||
if (line === 0) {
|
||||
return [];
|
||||
@@ -96,15 +96,15 @@ namespace ts.formatting {
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnEnter);
|
||||
}
|
||||
|
||||
export function formatOnSemicolon(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {
|
||||
export function formatOnSemicolon(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] {
|
||||
return formatOutermostParent(position, SyntaxKind.SemicolonToken, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnSemicolon);
|
||||
}
|
||||
|
||||
export function formatOnClosingCurly(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {
|
||||
export function formatOnClosingCurly(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] {
|
||||
return formatOutermostParent(position, SyntaxKind.CloseBraceToken, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnClosingCurlyBrace);
|
||||
}
|
||||
|
||||
export function formatDocument(sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {
|
||||
export function formatDocument(sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] {
|
||||
const span = {
|
||||
pos: 0,
|
||||
end: sourceFile.text.length
|
||||
@@ -112,7 +112,7 @@ namespace ts.formatting {
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatDocument);
|
||||
}
|
||||
|
||||
export function formatSelection(start: number, end: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {
|
||||
export function formatSelection(start: number, end: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] {
|
||||
// format from the beginning of the line
|
||||
const span = {
|
||||
pos: getLineStartPositionForPosition(start, sourceFile),
|
||||
@@ -121,7 +121,7 @@ namespace ts.formatting {
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatSelection);
|
||||
}
|
||||
|
||||
function formatOutermostParent(position: number, expectedLastToken: SyntaxKind, sourceFile: SourceFile, options: FormatCodeOptions, rulesProvider: RulesProvider, requestKind: FormattingRequestKind): TextChange[] {
|
||||
function formatOutermostParent(position: number, expectedLastToken: SyntaxKind, sourceFile: SourceFile, options: FormatCodeSettings, rulesProvider: RulesProvider, requestKind: FormattingRequestKind): TextChange[] {
|
||||
const parent = findOutermostParent(position, expectedLastToken, sourceFile);
|
||||
if (!parent) {
|
||||
return [];
|
||||
@@ -294,7 +294,7 @@ namespace ts.formatting {
|
||||
* if parent is on the different line - its delta was already contributed
|
||||
* to the initial indentation.
|
||||
*/
|
||||
function getOwnOrInheritedDelta(n: Node, options: FormatCodeOptions, sourceFile: SourceFile): number {
|
||||
function getOwnOrInheritedDelta(n: Node, options: FormatCodeSettings, sourceFile: SourceFile): number {
|
||||
let previousLine = Constants.Unknown;
|
||||
let child: Node;
|
||||
while (n) {
|
||||
@@ -304,7 +304,7 @@ namespace ts.formatting {
|
||||
}
|
||||
|
||||
if (SmartIndenter.shouldIndentChildNode(n, child)) {
|
||||
return options.IndentSize;
|
||||
return options.indentSize;
|
||||
}
|
||||
|
||||
previousLine = line;
|
||||
@@ -316,7 +316,7 @@ namespace ts.formatting {
|
||||
|
||||
function formatSpan(originalRange: TextRange,
|
||||
sourceFile: SourceFile,
|
||||
options: FormatCodeOptions,
|
||||
options: FormatCodeSettings,
|
||||
rulesProvider: RulesProvider,
|
||||
requestKind: FormattingRequestKind): TextChange[] {
|
||||
|
||||
@@ -413,7 +413,7 @@ namespace ts.formatting {
|
||||
effectiveParentStartLine: number): Indentation {
|
||||
|
||||
let indentation = inheritedIndentation;
|
||||
let delta = SmartIndenter.shouldIndentChildNode(node) ? options.IndentSize : 0;
|
||||
let delta = SmartIndenter.shouldIndentChildNode(node) ? options.indentSize : 0;
|
||||
|
||||
if (effectiveParentStartLine === startLine) {
|
||||
// if node is located on the same line with the parent
|
||||
@@ -422,7 +422,7 @@ namespace ts.formatting {
|
||||
indentation = startLine === lastIndentedLine
|
||||
? indentationOnLastIndentedLine
|
||||
: parentDynamicIndentation.getIndentation();
|
||||
delta = Math.min(options.IndentSize, parentDynamicIndentation.getDelta(node) + delta);
|
||||
delta = Math.min(options.indentSize, parentDynamicIndentation.getDelta(node) + delta);
|
||||
}
|
||||
else if (indentation === Constants.Unknown) {
|
||||
if (SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile)) {
|
||||
@@ -506,14 +506,14 @@ namespace ts.formatting {
|
||||
recomputeIndentation: lineAdded => {
|
||||
if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) {
|
||||
if (lineAdded) {
|
||||
indentation += options.IndentSize;
|
||||
indentation += options.indentSize;
|
||||
}
|
||||
else {
|
||||
indentation -= options.IndentSize;
|
||||
indentation -= options.indentSize;
|
||||
}
|
||||
|
||||
if (SmartIndenter.shouldIndentChildNode(node)) {
|
||||
delta = options.IndentSize;
|
||||
delta = options.indentSize;
|
||||
}
|
||||
else {
|
||||
delta = 0;
|
||||
@@ -1042,7 +1042,7 @@ namespace ts.formatting {
|
||||
// edit should not be applied only if we have one line feed between elements
|
||||
const lineDelta = currentStartLine - previousStartLine;
|
||||
if (lineDelta !== 1) {
|
||||
recordReplace(previousRange.end, currentRange.pos - previousRange.end, options.NewLineCharacter);
|
||||
recordReplace(previousRange.end, currentRange.pos - previousRange.end, options.newLineCharacter);
|
||||
}
|
||||
break;
|
||||
case RuleAction.Space:
|
||||
@@ -1108,19 +1108,19 @@ namespace ts.formatting {
|
||||
let internedTabsIndentation: string[];
|
||||
let internedSpacesIndentation: string[];
|
||||
|
||||
export function getIndentationString(indentation: number, options: FormatCodeOptions): string {
|
||||
export function getIndentationString(indentation: number, options: EditorSettings): string {
|
||||
// reset interned strings if FormatCodeOptions were changed
|
||||
const resetInternedStrings =
|
||||
!internedSizes || (internedSizes.tabSize !== options.TabSize || internedSizes.indentSize !== options.IndentSize);
|
||||
!internedSizes || (internedSizes.tabSize !== options.tabSize || internedSizes.indentSize !== options.indentSize);
|
||||
|
||||
if (resetInternedStrings) {
|
||||
internedSizes = { tabSize: options.TabSize, indentSize: options.IndentSize };
|
||||
internedSizes = { tabSize: options.tabSize, indentSize: options.indentSize };
|
||||
internedTabsIndentation = internedSpacesIndentation = undefined;
|
||||
}
|
||||
|
||||
if (!options.ConvertTabsToSpaces) {
|
||||
const tabs = Math.floor(indentation / options.TabSize);
|
||||
const spaces = indentation - tabs * options.TabSize;
|
||||
if (!options.convertTabsToSpaces) {
|
||||
const tabs = Math.floor(indentation / options.tabSize);
|
||||
const spaces = indentation - tabs * options.tabSize;
|
||||
|
||||
let tabString: string;
|
||||
if (!internedTabsIndentation) {
|
||||
@@ -1138,14 +1138,14 @@ namespace ts.formatting {
|
||||
}
|
||||
else {
|
||||
let spacesString: string;
|
||||
const quotient = Math.floor(indentation / options.IndentSize);
|
||||
const remainder = indentation % options.IndentSize;
|
||||
const quotient = Math.floor(indentation / options.indentSize);
|
||||
const remainder = indentation % options.indentSize;
|
||||
if (!internedSpacesIndentation) {
|
||||
internedSpacesIndentation = [];
|
||||
}
|
||||
|
||||
if (internedSpacesIndentation[quotient] === undefined) {
|
||||
spacesString = repeat(" ", options.IndentSize * quotient);
|
||||
spacesString = repeat(" ", options.indentSize * quotient);
|
||||
internedSpacesIndentation[quotient] = spacesString;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
namespace ts.formatting {
|
||||
export class RulesProvider {
|
||||
private globalRules: Rules;
|
||||
private options: ts.FormatCodeOptions;
|
||||
private options: ts.FormatCodeSettings;
|
||||
private activeRules: Rule[];
|
||||
private rulesMap: RulesMap;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace ts.formatting {
|
||||
return this.rulesMap;
|
||||
}
|
||||
|
||||
public ensureUpToDate(options: ts.FormatCodeOptions) {
|
||||
public ensureUpToDate(options: ts.FormatCodeSettings) {
|
||||
if (!this.options || !ts.compareDataObjects(this.options, options)) {
|
||||
const activeRules = this.createActiveRules(options);
|
||||
const rulesMap = RulesMap.create(activeRules);
|
||||
@@ -35,31 +35,31 @@ namespace ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
private createActiveRules(options: ts.FormatCodeOptions): Rule[] {
|
||||
private createActiveRules(options: ts.FormatCodeSettings): Rule[] {
|
||||
let rules = this.globalRules.HighPriorityCommonRules.slice(0);
|
||||
|
||||
if (options.InsertSpaceAfterCommaDelimiter) {
|
||||
if (options.insertSpaceAfterCommaDelimiter) {
|
||||
rules.push(this.globalRules.SpaceAfterComma);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterComma);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterFunctionKeywordForAnonymousFunctions) {
|
||||
if (options.insertSpaceAfterFunctionKeywordForAnonymousFunctions) {
|
||||
rules.push(this.globalRules.SpaceAfterAnonymousFunctionKeyword);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterAnonymousFunctionKeyword);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterKeywordsInControlFlowStatements) {
|
||||
if (options.insertSpaceAfterKeywordsInControlFlowStatements) {
|
||||
rules.push(this.globalRules.SpaceAfterKeywordInControl);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterKeywordInControl);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis) {
|
||||
if (options.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis) {
|
||||
rules.push(this.globalRules.SpaceAfterOpenParen);
|
||||
rules.push(this.globalRules.SpaceBeforeCloseParen);
|
||||
rules.push(this.globalRules.NoSpaceBetweenParens);
|
||||
@@ -70,7 +70,7 @@ namespace ts.formatting {
|
||||
rules.push(this.globalRules.NoSpaceBetweenParens);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets) {
|
||||
if (options.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets) {
|
||||
rules.push(this.globalRules.SpaceAfterOpenBracket);
|
||||
rules.push(this.globalRules.SpaceBeforeCloseBracket);
|
||||
rules.push(this.globalRules.NoSpaceBetweenBrackets);
|
||||
@@ -83,7 +83,7 @@ namespace ts.formatting {
|
||||
|
||||
// The default value of InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces is true
|
||||
// so if the option is undefined, we should treat it as true as well
|
||||
if (options.InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces !== false) {
|
||||
if (options.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces !== false) {
|
||||
rules.push(this.globalRules.SpaceAfterOpenBrace);
|
||||
rules.push(this.globalRules.SpaceBeforeCloseBrace);
|
||||
rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets);
|
||||
@@ -94,7 +94,7 @@ namespace ts.formatting {
|
||||
rules.push(this.globalRules.NoSpaceBetweenEmptyBraceBrackets);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces) {
|
||||
if (options.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces) {
|
||||
rules.push(this.globalRules.SpaceAfterTemplateHeadAndMiddle);
|
||||
rules.push(this.globalRules.SpaceBeforeTemplateMiddleAndTail);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace ts.formatting {
|
||||
rules.push(this.globalRules.NoSpaceBeforeTemplateMiddleAndTail);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces) {
|
||||
if (options.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces) {
|
||||
rules.push(this.globalRules.SpaceAfterOpenBraceInJsxExpression);
|
||||
rules.push(this.globalRules.SpaceBeforeCloseBraceInJsxExpression);
|
||||
}
|
||||
@@ -112,14 +112,14 @@ namespace ts.formatting {
|
||||
rules.push(this.globalRules.NoSpaceBeforeCloseBraceInJsxExpression);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterSemicolonInForStatements) {
|
||||
if (options.insertSpaceAfterSemicolonInForStatements) {
|
||||
rules.push(this.globalRules.SpaceAfterSemicolonInFor);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterSemicolonInFor);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceBeforeAndAfterBinaryOperators) {
|
||||
if (options.insertSpaceBeforeAndAfterBinaryOperators) {
|
||||
rules.push(this.globalRules.SpaceBeforeBinaryOperator);
|
||||
rules.push(this.globalRules.SpaceAfterBinaryOperator);
|
||||
}
|
||||
@@ -128,16 +128,16 @@ namespace ts.formatting {
|
||||
rules.push(this.globalRules.NoSpaceAfterBinaryOperator);
|
||||
}
|
||||
|
||||
if (options.PlaceOpenBraceOnNewLineForControlBlocks) {
|
||||
if (options.placeOpenBraceOnNewLineForControlBlocks) {
|
||||
rules.push(this.globalRules.NewLineBeforeOpenBraceInControl);
|
||||
}
|
||||
|
||||
if (options.PlaceOpenBraceOnNewLineForFunctions) {
|
||||
if (options.placeOpenBraceOnNewLineForFunctions) {
|
||||
rules.push(this.globalRules.NewLineBeforeOpenBraceInFunction);
|
||||
rules.push(this.globalRules.NewLineBeforeOpenBraceInTypeScriptDeclWithBlock);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterTypeAssertion) {
|
||||
if (options.insertSpaceAfterTypeAssertion) {
|
||||
rules.push(this.globalRules.SpaceAfterTypeAssertion);
|
||||
}
|
||||
else {
|
||||
@@ -149,4 +149,4 @@ namespace ts.formatting {
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,14 @@ namespace ts.formatting {
|
||||
Unknown = -1
|
||||
}
|
||||
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
if (position > sourceFile.text.length) {
|
||||
return getBaseIndentation(options); // past EOF
|
||||
}
|
||||
|
||||
// no indentation when the indent style is set to none,
|
||||
// so we can return fast
|
||||
if (options.IndentStyle === IndentStyle.None) {
|
||||
if (options.indentStyle === IndentStyle.None) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace ts.formatting {
|
||||
// indentation is first non-whitespace character in a previous line
|
||||
// for block indentation, we should look for a line which contains something that's not
|
||||
// whitespace.
|
||||
if (options.IndentStyle === IndentStyle.Block) {
|
||||
if (options.indentStyle === IndentStyle.Block) {
|
||||
|
||||
// move backwards until we find a line with a non-whitespace character,
|
||||
// then find the first non-whitespace character for that line.
|
||||
@@ -75,7 +75,7 @@ namespace ts.formatting {
|
||||
indentationDelta = 0;
|
||||
}
|
||||
else {
|
||||
indentationDelta = lineAtPosition !== currentStart.line ? options.IndentSize : 0;
|
||||
indentationDelta = lineAtPosition !== currentStart.line ? options.indentSize : 0;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -88,7 +88,7 @@ namespace ts.formatting {
|
||||
}
|
||||
actualIndentation = getLineIndentationWhenExpressionIsInMultiLine(current, sourceFile, options);
|
||||
if (actualIndentation !== Value.Unknown) {
|
||||
return actualIndentation + options.IndentSize;
|
||||
return actualIndentation + options.indentSize;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
@@ -103,22 +103,22 @@ namespace ts.formatting {
|
||||
return getIndentationForNodeWorker(current, currentStart, /*ignoreActualIndentationRange*/ undefined, indentationDelta, sourceFile, options);
|
||||
}
|
||||
|
||||
export function getBaseIndentation(options: EditorOptions) {
|
||||
return options.BaseIndentSize || 0;
|
||||
}
|
||||
|
||||
export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: FormatCodeOptions): number {
|
||||
export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
const start = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));
|
||||
return getIndentationForNodeWorker(n, start, ignoreActualIndentationRange, /*indentationDelta*/ 0, sourceFile, options);
|
||||
}
|
||||
|
||||
export function getBaseIndentation(options: EditorSettings) {
|
||||
return options.baseIndentSize || 0;
|
||||
}
|
||||
|
||||
function getIndentationForNodeWorker(
|
||||
current: Node,
|
||||
currentStart: LineAndCharacter,
|
||||
ignoreActualIndentationRange: TextRange,
|
||||
indentationDelta: number,
|
||||
sourceFile: SourceFile,
|
||||
options: EditorOptions): number {
|
||||
options: EditorSettings): number {
|
||||
|
||||
let parent: Node = current.parent;
|
||||
let parentStart: LineAndCharacter;
|
||||
@@ -158,7 +158,7 @@ namespace ts.formatting {
|
||||
|
||||
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
|
||||
if (shouldIndentChildNode(parent, current) && !parentAndChildShareLine) {
|
||||
indentationDelta += options.IndentSize;
|
||||
indentationDelta += options.indentSize;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
@@ -182,7 +182,7 @@ namespace ts.formatting {
|
||||
/*
|
||||
* Function returns Value.Unknown if indentation cannot be determined
|
||||
*/
|
||||
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||
const commaItemInfo = findListItemInfo(commaToken);
|
||||
if (commaItemInfo && commaItemInfo.listItemIndex > 0) {
|
||||
@@ -202,7 +202,7 @@ namespace ts.formatting {
|
||||
currentLineAndChar: LineAndCharacter,
|
||||
parentAndChildShareLine: boolean,
|
||||
sourceFile: SourceFile,
|
||||
options: EditorOptions): number {
|
||||
options: EditorSettings): number {
|
||||
|
||||
// actual indentation is used for statements\declarations if one of cases below is true:
|
||||
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
|
||||
@@ -309,7 +309,7 @@ namespace ts.formatting {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
const containingList = getContainingList(node, sourceFile);
|
||||
return containingList ? getActualIndentationFromList(containingList) : Value.Unknown;
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
function getLineIndentationWhenExpressionIsInMultiLine(node: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
function getLineIndentationWhenExpressionIsInMultiLine(node: Node, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
// actual indentation should not be used when:
|
||||
// - node is close parenthesis - this is the end of the expression
|
||||
if (node.kind === SyntaxKind.CloseParenToken) {
|
||||
@@ -367,7 +367,7 @@ namespace ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
Debug.assert(index >= 0 && index < list.length);
|
||||
const node = list[index];
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace ts.formatting {
|
||||
return Value.Unknown;
|
||||
}
|
||||
|
||||
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
const lineStart = sourceFile.getPositionOfLineAndCharacter(lineAndCharacter.line, 0);
|
||||
return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options);
|
||||
}
|
||||
@@ -401,7 +401,7 @@ namespace ts.formatting {
|
||||
value of 'character' for '$' is 3
|
||||
value of 'column' for '$' is 6 (assuming that tab size is 4)
|
||||
*/
|
||||
export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorOptions) {
|
||||
export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorSettings) {
|
||||
let character = 0;
|
||||
let column = 0;
|
||||
for (let pos = startPos; pos < endPos; pos++) {
|
||||
@@ -411,7 +411,7 @@ namespace ts.formatting {
|
||||
}
|
||||
|
||||
if (ch === CharacterCodes.tab) {
|
||||
column += options.TabSize + (column % options.TabSize);
|
||||
column += options.tabSize + (column % options.tabSize);
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
@@ -422,7 +422,7 @@ namespace ts.formatting {
|
||||
return { column, character };
|
||||
}
|
||||
|
||||
export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
|
||||
// See LICENSE.txt in the project root for complete license information.
|
||||
|
||||
/// <reference path='services.ts' />
|
||||
/// <reference path='../compiler/types.ts' />
|
||||
/// <reference path='../compiler/core.ts' />
|
||||
/// <reference path='../compiler/commandLineParser.ts' />
|
||||
|
||||
/* @internal */
|
||||
namespace ts.JsTyping {
|
||||
@@ -27,6 +29,8 @@ namespace ts.JsTyping {
|
||||
// that we are confident require typings
|
||||
let safeList: Map<string>;
|
||||
|
||||
const EmptySafeList: Map<string> = createMap<string>();
|
||||
|
||||
/**
|
||||
* @param host is the object providing I/O related operations.
|
||||
* @param fileNames are the file names that belong to the same project
|
||||
@@ -54,11 +58,14 @@ namespace ts.JsTyping {
|
||||
}
|
||||
|
||||
// Only infer typings for .js and .jsx files
|
||||
fileNames = filter(map(fileNames, normalizePath), f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JS, ScriptKind.JSX));
|
||||
fileNames = filter(map(fileNames, normalizePath), f => {
|
||||
const kind = ensureScriptKind(f, getScriptKindFromFileName(f));
|
||||
return kind === ScriptKind.JS || kind === ScriptKind.JSX;
|
||||
});
|
||||
|
||||
if (!safeList) {
|
||||
const result = readConfigFile(safeListPath, (path: string) => host.readFile(path));
|
||||
safeList = createMap<string>(result.config);
|
||||
safeList = result.config ? createMap<string>(result.config) : EmptySafeList;
|
||||
}
|
||||
|
||||
const filesToWatch: string[] = [];
|
||||
@@ -158,14 +165,12 @@ namespace ts.JsTyping {
|
||||
const jsFileNames = filter(fileNames, hasJavaScriptFileExtension);
|
||||
const inferredTypingNames = map(jsFileNames, f => removeFileExtension(getBaseFileName(f.toLowerCase())));
|
||||
const cleanedTypingNames = map(inferredTypingNames, f => f.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, ""));
|
||||
if (safeList === undefined) {
|
||||
mergeTypings(cleanedTypingNames);
|
||||
}
|
||||
else {
|
||||
|
||||
if (safeList !== EmptySafeList) {
|
||||
mergeTypings(filter(cleanedTypingNames, f => f in safeList));
|
||||
}
|
||||
|
||||
const hasJsxFile = forEach(fileNames, f => scriptKindIs(f, /*LanguageServiceHost*/ undefined, ScriptKind.JSX));
|
||||
const hasJsxFile = forEach(fileNames, f => ensureScriptKind(f, getScriptKindFromFileName(f)) === ScriptKind.JSX);
|
||||
if (hasJsxFile) {
|
||||
mergeTypings(["react"]);
|
||||
}
|
||||
@@ -182,7 +187,7 @@ namespace ts.JsTyping {
|
||||
}
|
||||
|
||||
const typingNames: string[] = [];
|
||||
const fileNames = host.readDirectory(nodeModulesPath, ["*.json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
|
||||
const fileNames = host.readDirectory(nodeModulesPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
|
||||
for (const fileName of fileNames) {
|
||||
const normalizedFileName = normalizePath(fileName);
|
||||
if (getBaseFileName(normalizedFileName) !== "package.json") {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace ts.NavigateTo {
|
||||
type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration };
|
||||
|
||||
export function getNavigateToItems(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number): NavigateToItem[] {
|
||||
export function getNavigateToItems(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number, excludeDtsFiles: boolean): NavigateToItem[] {
|
||||
const patternMatcher = createPatternMatcher(searchValue);
|
||||
let rawItems: RawNavigateToItem[] = [];
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace ts.NavigateTo {
|
||||
forEach(sourceFiles, sourceFile => {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
if (excludeDtsFiles && fileExtensionIs(sourceFile.fileName, ".d.ts")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nameToDeclarations = sourceFile.getNamedDeclarations();
|
||||
for (const name in nameToDeclarations) {
|
||||
const declarations = nameToDeclarations[name];
|
||||
|
||||
+57
-16
@@ -677,6 +677,34 @@ namespace ts {
|
||||
displayParts(): SymbolDisplayPart[];
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings;
|
||||
export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings;
|
||||
export function toEditorSettings(optionsAsMap: MapLike<any>): MapLike<any> {
|
||||
let allPropertiesAreCamelCased = true;
|
||||
for (const key in optionsAsMap) {
|
||||
if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) {
|
||||
allPropertiesAreCamelCased = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allPropertiesAreCamelCased) {
|
||||
return optionsAsMap;
|
||||
}
|
||||
const settings: MapLike<any> = {};
|
||||
for (const key in optionsAsMap) {
|
||||
if (hasProperty(optionsAsMap, key)) {
|
||||
const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1);
|
||||
settings[newKey] = optionsAsMap[key];
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
function isCamelCase(s: string) {
|
||||
return !s.length || s.charAt(0) === s.charAt(0).toLowerCase();
|
||||
}
|
||||
|
||||
export function displayPartsToString(displayParts: SymbolDisplayPart[]) {
|
||||
if (displayParts) {
|
||||
return map(displayParts, displayPart => displayPart.text).join("");
|
||||
@@ -917,7 +945,9 @@ namespace ts {
|
||||
let program: Program;
|
||||
let lastProjectVersion: string;
|
||||
|
||||
const useCaseSensitivefileNames = false;
|
||||
let lastTypesRootVersion = 0;
|
||||
|
||||
const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames();
|
||||
const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());
|
||||
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
@@ -942,7 +972,7 @@ namespace ts {
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
function getRuleProvider(options: FormatCodeOptions) {
|
||||
function getRuleProvider(options: FormatCodeSettings) {
|
||||
// Ensure rules are initialized and up to date wrt to formatting options
|
||||
if (!ruleProvider) {
|
||||
ruleProvider = new formatting.RulesProvider();
|
||||
@@ -965,6 +995,13 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0;
|
||||
if (lastTypesRootVersion !== typeRootsVersion) {
|
||||
log("TypeRoots version has changed; provide new program");
|
||||
program = undefined;
|
||||
lastTypesRootVersion = typeRootsVersion;
|
||||
}
|
||||
|
||||
// Get a fresh cache of the host information
|
||||
let hostCache = new HostCache(host, getCanonicalFileName);
|
||||
|
||||
@@ -1355,14 +1392,14 @@ namespace ts {
|
||||
}
|
||||
|
||||
/// NavigateTo
|
||||
function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): NavigateToItem[] {
|
||||
function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[] {
|
||||
synchronizeHostData();
|
||||
|
||||
const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles();
|
||||
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount);
|
||||
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
|
||||
}
|
||||
|
||||
function getEmitOutput(fileName: string): EmitOutput {
|
||||
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput {
|
||||
synchronizeHostData();
|
||||
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
@@ -1376,7 +1413,7 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken);
|
||||
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles);
|
||||
|
||||
return {
|
||||
outputFiles,
|
||||
@@ -1552,40 +1589,44 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions) {
|
||||
function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) {
|
||||
let start = timestamp();
|
||||
const settings = toEditorSettings(editorOptions);
|
||||
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
|
||||
log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start));
|
||||
|
||||
start = timestamp();
|
||||
|
||||
const result = formatting.SmartIndenter.getIndentation(position, sourceFile, editorOptions);
|
||||
const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings);
|
||||
log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[] {
|
||||
function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
|
||||
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
|
||||
return formatting.formatSelection(start, end, sourceFile, getRuleProvider(options), options);
|
||||
const settings = toEditorSettings(options);
|
||||
return formatting.formatSelection(start, end, sourceFile, getRuleProvider(settings), settings);
|
||||
}
|
||||
|
||||
function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] {
|
||||
function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
|
||||
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
|
||||
return formatting.formatDocument(sourceFile, getRuleProvider(options), options);
|
||||
const settings = toEditorSettings(options);
|
||||
return formatting.formatDocument(sourceFile, getRuleProvider(settings), settings);
|
||||
}
|
||||
|
||||
function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[] {
|
||||
function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
|
||||
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
|
||||
const settings = toEditorSettings(options);
|
||||
|
||||
if (key === "}") {
|
||||
return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(options), options);
|
||||
return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings);
|
||||
}
|
||||
else if (key === ";") {
|
||||
return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(options), options);
|
||||
return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings);
|
||||
}
|
||||
else if (key === "\n") {
|
||||
return formatting.formatOnEnter(position, sourceFile, getRuleProvider(options), options);
|
||||
return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/// <reference path='services.ts' />
|
||||
|
||||
/* @internal */
|
||||
let debugObjectHost = new Function("return this")();
|
||||
let debugObjectHost = (function (this: any) { return this; })();
|
||||
|
||||
// We need to use 'null' to interface with the managed side.
|
||||
/* tslint:disable:no-null-keyword */
|
||||
@@ -67,6 +67,7 @@ namespace ts {
|
||||
getProjectVersion?(): string;
|
||||
useCaseSensitiveFileNames?(): boolean;
|
||||
|
||||
getTypeRootsVersion?(): number;
|
||||
readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string;
|
||||
readFile(path: string, encoding?: string): string;
|
||||
fileExists(path: string): boolean;
|
||||
@@ -364,6 +365,13 @@ namespace ts {
|
||||
return this.shimHost.getProjectVersion();
|
||||
}
|
||||
|
||||
public getTypeRootsVersion(): number {
|
||||
if (!this.shimHost.getTypeRootsVersion) {
|
||||
return 0;
|
||||
}
|
||||
return this.shimHost.getTypeRootsVersion();
|
||||
}
|
||||
|
||||
public useCaseSensitiveFileNames(): boolean {
|
||||
return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false;
|
||||
}
|
||||
|
||||
+42
-11
@@ -148,6 +148,11 @@ namespace ts {
|
||||
readFile?(path: string, encoding?: string): string;
|
||||
fileExists?(path: string): boolean;
|
||||
|
||||
/*
|
||||
* LS host can optionally implement these methods to support automatic updating when new type libraries are installed
|
||||
*/
|
||||
getTypeRootsVersion?(): number;
|
||||
|
||||
/*
|
||||
* LS host can optionally implement this method if it wants to be completely in charge of module name resolution.
|
||||
* if implementation is omitted then language service will use built-in module resolution logic and get answers to
|
||||
@@ -218,23 +223,23 @@ namespace ts {
|
||||
/** @deprecated */
|
||||
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
|
||||
|
||||
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): NavigateToItem[];
|
||||
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): 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;
|
||||
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): 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[];
|
||||
getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[];
|
||||
getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[];
|
||||
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[];
|
||||
|
||||
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion;
|
||||
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
|
||||
getEmitOutput(fileName: string): EmitOutput;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
|
||||
getProgram(): Program;
|
||||
|
||||
@@ -339,6 +344,13 @@ namespace ts {
|
||||
containerKind: string;
|
||||
}
|
||||
|
||||
export enum IndentStyle {
|
||||
None = 0,
|
||||
Block = 1,
|
||||
Smart = 2,
|
||||
}
|
||||
|
||||
/* @deprecated - consider using EditorSettings instead */
|
||||
export interface EditorOptions {
|
||||
BaseIndentSize?: number;
|
||||
IndentSize: number;
|
||||
@@ -348,12 +360,16 @@ namespace ts {
|
||||
IndentStyle: IndentStyle;
|
||||
}
|
||||
|
||||
export enum IndentStyle {
|
||||
None = 0,
|
||||
Block = 1,
|
||||
Smart = 2,
|
||||
export interface EditorSettings {
|
||||
baseIndentSize?: number;
|
||||
indentSize: number;
|
||||
tabSize: number;
|
||||
newLineCharacter: string;
|
||||
convertTabsToSpaces: boolean;
|
||||
indentStyle: IndentStyle;
|
||||
}
|
||||
|
||||
/* @deprecated - consider using FormatCodeSettings instead */
|
||||
export interface FormatCodeOptions extends EditorOptions {
|
||||
InsertSpaceAfterCommaDelimiter: boolean;
|
||||
InsertSpaceAfterSemicolonInForStatements: boolean;
|
||||
@@ -368,7 +384,22 @@ namespace ts {
|
||||
InsertSpaceAfterTypeAssertion?: boolean;
|
||||
PlaceOpenBraceOnNewLineForFunctions: boolean;
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
|
||||
[s: string]: boolean | number | string | undefined;
|
||||
}
|
||||
|
||||
export interface FormatCodeSettings extends EditorSettings {
|
||||
insertSpaceAfterCommaDelimiter: boolean;
|
||||
insertSpaceAfterSemicolonInForStatements: boolean;
|
||||
insertSpaceBeforeAndAfterBinaryOperators: boolean;
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: boolean;
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean;
|
||||
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: boolean;
|
||||
insertSpaceAfterTypeAssertion?: boolean;
|
||||
placeOpenBraceOnNewLineForFunctions: boolean;
|
||||
placeOpenBraceOnNewLineForControlBlocks: boolean;
|
||||
}
|
||||
|
||||
export interface DefinitionInfo {
|
||||
|
||||
@@ -1335,7 +1335,7 @@ namespace ts {
|
||||
return ensureScriptKind(fileName, scriptKind);
|
||||
}
|
||||
|
||||
export function parseAndReEmitConfigJSONFile(content: string) {
|
||||
export function sanitizeConfigFile(configFileName: string, content: string) {
|
||||
const options: TranspileOptions = {
|
||||
fileName: "config.js",
|
||||
compilerOptions: {
|
||||
@@ -1349,10 +1349,13 @@ namespace ts {
|
||||
// also, the emitted result will have "(" in the beginning and ");" in the end. We need to strip these
|
||||
// as well
|
||||
const trimmedOutput = outputText.trim();
|
||||
const configJsonObject = JSON.parse(trimmedOutput.substring(1, trimmedOutput.length - 2));
|
||||
for (const diagnostic of diagnostics) {
|
||||
diagnostic.start = diagnostic.start - 1;
|
||||
}
|
||||
return { configJsonObject, diagnostics };
|
||||
const {config, error} = parseConfigFileTextToJson(configFileName, trimmedOutput.substring(1, trimmedOutput.length - 2), /*stripComments*/ false);
|
||||
return {
|
||||
configJsonObject: config || {},
|
||||
diagnostics: error ? concatenate(diagnostics, [error]) : diagnostics
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -56,7 +56,6 @@ var clodule1 = (function () {
|
||||
}
|
||||
return clodule1;
|
||||
}());
|
||||
var clodule1;
|
||||
(function (clodule1) {
|
||||
function f(x) { }
|
||||
})(clodule1 || (clodule1 = {}));
|
||||
@@ -65,7 +64,6 @@ var clodule2 = (function () {
|
||||
}
|
||||
return clodule2;
|
||||
}());
|
||||
var clodule2;
|
||||
(function (clodule2) {
|
||||
var x;
|
||||
var D = (function () {
|
||||
@@ -79,7 +77,6 @@ var clodule3 = (function () {
|
||||
}
|
||||
return clodule3;
|
||||
}());
|
||||
var clodule3;
|
||||
(function (clodule3) {
|
||||
clodule3.y = { id: T };
|
||||
})(clodule3 || (clodule3 = {}));
|
||||
@@ -88,7 +85,6 @@ var clodule4 = (function () {
|
||||
}
|
||||
return clodule4;
|
||||
}());
|
||||
var clodule4;
|
||||
(function (clodule4) {
|
||||
var D = (function () {
|
||||
function D() {
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ var clodule = (function () {
|
||||
clodule.fn = function (id) { };
|
||||
return clodule;
|
||||
}());
|
||||
var clodule;
|
||||
(function (clodule) {
|
||||
// error: duplicate identifier expected
|
||||
function fn(x, y) {
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ var clodule = (function () {
|
||||
clodule.fn = function (id) { };
|
||||
return clodule;
|
||||
}());
|
||||
var clodule;
|
||||
(function (clodule) {
|
||||
// error: duplicate identifier expected
|
||||
function fn(x, y) {
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ var clodule = (function () {
|
||||
clodule.sfn = function (id) { return 42; };
|
||||
return clodule;
|
||||
}());
|
||||
var clodule;
|
||||
(function (clodule) {
|
||||
// error: duplicate identifier expected
|
||||
function fn(x, y) {
|
||||
|
||||
-2
@@ -31,7 +31,6 @@ var Point = (function () {
|
||||
Point.Origin = function () { return { x: 0, y: 0 }; }; // unexpected error here bug 840246
|
||||
return Point;
|
||||
}());
|
||||
var Point;
|
||||
(function (Point) {
|
||||
function Origin() { return null; } //expected duplicate identifier error
|
||||
Point.Origin = Origin;
|
||||
@@ -47,7 +46,6 @@ var A;
|
||||
return Point;
|
||||
}());
|
||||
A.Point = Point;
|
||||
var Point;
|
||||
(function (Point) {
|
||||
function Origin() { return ""; } //expected duplicate identifier error
|
||||
Point.Origin = Origin;
|
||||
|
||||
-2
@@ -31,7 +31,6 @@ var Point = (function () {
|
||||
Point.Origin = function () { return { x: 0, y: 0 }; };
|
||||
return Point;
|
||||
}());
|
||||
var Point;
|
||||
(function (Point) {
|
||||
function Origin() { return ""; } // not an error, since not exported
|
||||
})(Point || (Point = {}));
|
||||
@@ -46,7 +45,6 @@ var A;
|
||||
return Point;
|
||||
}());
|
||||
A.Point = Point;
|
||||
var Point;
|
||||
(function (Point) {
|
||||
function Origin() { return ""; } // not an error since not exported
|
||||
})(Point = A.Point || (A.Point = {}));
|
||||
|
||||
-2
@@ -31,7 +31,6 @@ var Point = (function () {
|
||||
return Point;
|
||||
}());
|
||||
Point.Origin = { x: 0, y: 0 };
|
||||
var Point;
|
||||
(function (Point) {
|
||||
Point.Origin = ""; //expected duplicate identifier error
|
||||
})(Point || (Point = {}));
|
||||
@@ -46,7 +45,6 @@ var A;
|
||||
}());
|
||||
Point.Origin = { x: 0, y: 0 };
|
||||
A.Point = Point;
|
||||
var Point;
|
||||
(function (Point) {
|
||||
Point.Origin = ""; //expected duplicate identifier error
|
||||
})(Point = A.Point || (A.Point = {}));
|
||||
|
||||
-2
@@ -31,7 +31,6 @@ var Point = (function () {
|
||||
return Point;
|
||||
}());
|
||||
Point.Origin = { x: 0, y: 0 };
|
||||
var Point;
|
||||
(function (Point) {
|
||||
var Origin = ""; // not an error, since not exported
|
||||
})(Point || (Point = {}));
|
||||
@@ -46,7 +45,6 @@ var A;
|
||||
}());
|
||||
Point.Origin = { x: 0, y: 0 };
|
||||
A.Point = Point;
|
||||
var Point;
|
||||
(function (Point) {
|
||||
var Origin = ""; // not an error since not exported
|
||||
})(Point = A.Point || (A.Point = {}));
|
||||
|
||||
@@ -76,7 +76,6 @@ var A = (function () {
|
||||
}
|
||||
return A;
|
||||
}());
|
||||
var A;
|
||||
(function (A) {
|
||||
A.Instance = new A();
|
||||
})(A || (A = {}));
|
||||
|
||||
@@ -22,7 +22,6 @@ var enumdule;
|
||||
enumdule[enumdule["Red"] = 0] = "Red";
|
||||
enumdule[enumdule["Blue"] = 1] = "Blue";
|
||||
})(enumdule || (enumdule = {}));
|
||||
var enumdule;
|
||||
(function (enumdule) {
|
||||
var Point = (function () {
|
||||
function Point(x, y) {
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ var A;
|
||||
var Point3d = (function (_super) {
|
||||
__extends(Point3d, _super);
|
||||
function Point3d() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return Point3d;
|
||||
}(Point));
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ var A;
|
||||
var Point3d = (function (_super) {
|
||||
__extends(Point3d, _super);
|
||||
function Point3d() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return Point3d;
|
||||
}(Point));
|
||||
|
||||
@@ -72,7 +72,6 @@ var B;
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
B.Point = Point;
|
||||
var Point;
|
||||
(function (Point) {
|
||||
Point.Origin = { x: 0, y: 0 };
|
||||
})(Point = B.Point || (B.Point = {}));
|
||||
|
||||
@@ -28,7 +28,6 @@ var enumdule;
|
||||
}());
|
||||
enumdule.Point = Point;
|
||||
})(enumdule || (enumdule = {}));
|
||||
var enumdule;
|
||||
(function (enumdule) {
|
||||
enumdule[enumdule["Red"] = 0] = "Red";
|
||||
enumdule[enumdule["Blue"] = 1] = "Blue";
|
||||
|
||||
-2
@@ -50,7 +50,6 @@ var A;
|
||||
}());
|
||||
A.Point = Point;
|
||||
})(A || (A = {}));
|
||||
var A;
|
||||
(function (A) {
|
||||
var Point = (function () {
|
||||
function Point() {
|
||||
@@ -79,7 +78,6 @@ var X;
|
||||
})(Z = Y.Z || (Y.Z = {}));
|
||||
})(Y = X.Y || (X.Y = {}));
|
||||
})(X || (X = {}));
|
||||
var X;
|
||||
(function (X) {
|
||||
var Y;
|
||||
(function (Y) {
|
||||
|
||||
-2
@@ -42,7 +42,6 @@ var A;
|
||||
}());
|
||||
A.Point = Point;
|
||||
})(A || (A = {}));
|
||||
var A;
|
||||
(function (A) {
|
||||
// expected error
|
||||
var Point = (function () {
|
||||
@@ -67,7 +66,6 @@ var X;
|
||||
})(Z = Y.Z || (Y.Z = {}));
|
||||
})(Y = X.Y || (X.Y = {}));
|
||||
})(X || (X = {}));
|
||||
var X;
|
||||
(function (X) {
|
||||
var Y;
|
||||
(function (Y) {
|
||||
|
||||
-2
@@ -41,7 +41,6 @@ var A;
|
||||
(function (B) {
|
||||
})(B = A.B || (A.B = {}));
|
||||
})(A || (A = {}));
|
||||
var A;
|
||||
(function (A) {
|
||||
var B;
|
||||
(function (B) {
|
||||
@@ -65,7 +64,6 @@ var X;
|
||||
})(Z = Y.Z || (Y.Z = {}));
|
||||
})(Y = X.Y || (X.Y = {}));
|
||||
})(X || (X = {}));
|
||||
var X;
|
||||
(function (X) {
|
||||
var Y;
|
||||
(function (Y) {
|
||||
|
||||
@@ -22,7 +22,7 @@ var __extends = (this && this.__extends) || function (d, b) {
|
||||
var B = (function (_super) {
|
||||
__extends(B, _super);
|
||||
function B() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return B;
|
||||
}(A));
|
||||
|
||||
@@ -22,7 +22,7 @@ var __extends = (this && this.__extends) || function (d, b) {
|
||||
var B = (function (_super) {
|
||||
__extends(B, _super);
|
||||
function B() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return B;
|
||||
}(A));
|
||||
|
||||
@@ -35,9 +35,10 @@ var B = (function () {
|
||||
var C = (function (_super) {
|
||||
__extends(C, _super);
|
||||
function C() {
|
||||
_super.apply(this, arguments);
|
||||
this.raw = "edge";
|
||||
this.ro = "readonly please";
|
||||
var _this = _super.apply(this, arguments) || this;
|
||||
_this.raw = "edge";
|
||||
_this.ro = "readonly please";
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(C.prototype, "prop", {
|
||||
get: function () { return "foo"; },
|
||||
|
||||
@@ -57,8 +57,9 @@ var B = (function () {
|
||||
var C = (function (_super) {
|
||||
__extends(C, _super);
|
||||
function C() {
|
||||
_super.apply(this, arguments);
|
||||
this.ro = "readonly please";
|
||||
var _this = _super.apply(this, arguments) || this;
|
||||
_this.ro = "readonly please";
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(C.prototype, "concreteWithNoBody", {
|
||||
get: function () { },
|
||||
@@ -77,8 +78,9 @@ var WrongTypeProperty = (function () {
|
||||
var WrongTypePropertyImpl = (function (_super) {
|
||||
__extends(WrongTypePropertyImpl, _super);
|
||||
function WrongTypePropertyImpl() {
|
||||
_super.apply(this, arguments);
|
||||
this.num = "nope, wrong";
|
||||
var _this = _super.apply(this, arguments) || this;
|
||||
_this.num = "nope, wrong";
|
||||
return _this;
|
||||
}
|
||||
return WrongTypePropertyImpl;
|
||||
}(WrongTypeProperty));
|
||||
@@ -90,7 +92,7 @@ var WrongTypeAccessor = (function () {
|
||||
var WrongTypeAccessorImpl = (function (_super) {
|
||||
__extends(WrongTypeAccessorImpl, _super);
|
||||
function WrongTypeAccessorImpl() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
Object.defineProperty(WrongTypeAccessorImpl.prototype, "num", {
|
||||
get: function () { return "nope, wrong"; },
|
||||
@@ -102,8 +104,9 @@ var WrongTypeAccessorImpl = (function (_super) {
|
||||
var WrongTypeAccessorImpl2 = (function (_super) {
|
||||
__extends(WrongTypeAccessorImpl2, _super);
|
||||
function WrongTypeAccessorImpl2() {
|
||||
_super.apply(this, arguments);
|
||||
this.num = "nope, wrong";
|
||||
var _this = _super.apply(this, arguments) || this;
|
||||
_this.num = "nope, wrong";
|
||||
return _this;
|
||||
}
|
||||
return WrongTypeAccessorImpl2;
|
||||
}(WrongTypeAccessor));
|
||||
|
||||
@@ -34,8 +34,9 @@ var Point = (function () {
|
||||
var ColoredPoint = (function (_super) {
|
||||
__extends(ColoredPoint, _super);
|
||||
function ColoredPoint(x, y, color) {
|
||||
_super.call(this, x, y);
|
||||
this.color = color;
|
||||
var _this = _super.call(this, x, y) || this;
|
||||
_this.color = color;
|
||||
return _this;
|
||||
}
|
||||
ColoredPoint.prototype.toString = function () {
|
||||
return _super.prototype.toString.call(this) + " color=" + this.color;
|
||||
|
||||
@@ -38,7 +38,7 @@ var A = (function () {
|
||||
var B = (function (_super) {
|
||||
__extends(B, _super);
|
||||
function B() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return B;
|
||||
}(A));
|
||||
|
||||
@@ -46,7 +46,7 @@ var Backbone = require("./aliasUsage1_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -40,7 +40,7 @@ var Backbone = require("./aliasUsageInArray_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -39,7 +39,7 @@ var Backbone = require("./aliasUsageInFunctionExpression_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -43,7 +43,7 @@ var Backbone = require("./aliasUsageInGenericFunction_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -45,7 +45,7 @@ var Backbone = require("./aliasUsageInIndexerOfClass_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -40,7 +40,7 @@ var Backbone = require("./aliasUsageInObjectLiteral_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -43,7 +43,7 @@ var Backbone = require("./aliasUsageInOrExpression_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
|
||||
@@ -43,7 +43,7 @@ var Backbone = require("./aliasUsageInTypeArgumentOfExtendsClause_backbone");
|
||||
var VisualizationModel = (function (_super) {
|
||||
__extends(VisualizationModel, _super);
|
||||
function VisualizationModel() {
|
||||
_super.apply(this, arguments);
|
||||
return _super.apply(this, arguments) || this;
|
||||
}
|
||||
return VisualizationModel;
|
||||
}(Backbone.Model));
|
||||
@@ -64,8 +64,9 @@ var C = (function () {
|
||||
var D = (function (_super) {
|
||||
__extends(D, _super);
|
||||
function D() {
|
||||
_super.apply(this, arguments);
|
||||
this.x = moduleA;
|
||||
var _this = _super.apply(this, arguments) || this;
|
||||
_this.x = moduleA;
|
||||
return _this;
|
||||
}
|
||||
return D;
|
||||
}(C));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user