mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 20:25:23 -06:00
Merge branch 'tsserverVS-WIP' into tsserverVS-Types2.0
# Conflicts: # src/server/typingsCache.ts
This commit is contained in:
commit
7132fe5112
@ -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",
|
||||
@ -808,6 +811,7 @@ namespace ts {
|
||||
options.configFilePath = configFileName;
|
||||
|
||||
const { fileNames, wildcardDirectories } = getFileNames(errors);
|
||||
const compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
|
||||
|
||||
return {
|
||||
options,
|
||||
@ -815,7 +819,8 @@ namespace ts {
|
||||
typingOptions,
|
||||
raw: json,
|
||||
errors,
|
||||
wildcardDirectories
|
||||
wildcardDirectories,
|
||||
compileOnSave
|
||||
};
|
||||
|
||||
function getFileNames(errors: Diagnostic[]): ExpandResult {
|
||||
@ -870,13 +875,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 };
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/// <reference path="types.ts"/>
|
||||
/// <reference path="types.ts"/>
|
||||
/// <reference path="performance.ts" />
|
||||
/// <reference path="diagnosticInformationMap.generated.ts" />
|
||||
|
||||
|
||||
/* @internal */
|
||||
@ -47,6 +48,7 @@ namespace ts {
|
||||
contains,
|
||||
remove,
|
||||
forEachValue: forEachValueInMap,
|
||||
getKeys,
|
||||
clear,
|
||||
};
|
||||
|
||||
@ -56,6 +58,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)];
|
||||
@ -311,18 +321,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 {
|
||||
|
||||
@ -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);
|
||||
@ -1713,7 +1713,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)) {
|
||||
@ -1722,7 +1722,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// Get the declaration file path
|
||||
forEachExpectedEmitFile(host, getDeclFileName, referencedFile);
|
||||
forEachExpectedEmitFile(host, getDeclFileName, referencedFile, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
if (declFileName) {
|
||||
@ -1751,8 +1751,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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/// <reference path="checker.ts"/>
|
||||
/// <reference path="checker.ts"/>
|
||||
/// <reference path="sourcemap.ts" />
|
||||
/// <reference path="declarationEmitter.ts"/>
|
||||
|
||||
@ -336,7 +336,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// emit output for the __extends helper function
|
||||
const extendsHelper = `
|
||||
var __extends = (this && this.__extends) || function (d, b) {
|
||||
@ -396,7 +396,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
const newLine = host.getNewLine();
|
||||
|
||||
const emitJavaScript = createFileEmitter();
|
||||
forEachExpectedEmitFile(host, emitFile, targetSourceFile);
|
||||
forEachExpectedEmitFile(host, emitFile, targetSourceFile, emitOnlyDtsFiles);
|
||||
|
||||
return {
|
||||
emitSkipped,
|
||||
@ -1615,7 +1615,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
else if (declaration.kind === SyntaxKind.ImportSpecifier) {
|
||||
// Identifier references named import
|
||||
write(getGeneratedNameForNode(<ImportDeclaration>declaration.parent.parent.parent));
|
||||
const name = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
|
||||
const name = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
|
||||
const identifier = getTextOfNodeFromSourceText(currentText, name);
|
||||
if (languageVersion === ScriptTarget.ES3 && identifier === "default") {
|
||||
write('["default"]');
|
||||
@ -3254,19 +3254,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
write("var ");
|
||||
let seen: Map<string>;
|
||||
for (const id of convertedLoopState.hoistedLocalVariables) {
|
||||
// Don't initialize seen unless we have at least one element.
|
||||
// Emit a comma to separate for all but the first element.
|
||||
if (!seen) {
|
||||
// Don't initialize seen unless we have at least one element.
|
||||
// Emit a comma to separate for all but the first element.
|
||||
if (!seen) {
|
||||
seen = createMap<string>();
|
||||
}
|
||||
else {
|
||||
write(", ");
|
||||
}
|
||||
}
|
||||
else {
|
||||
write(", ");
|
||||
}
|
||||
|
||||
if (!(id.text in seen)) {
|
||||
emit(id);
|
||||
seen[id.text] = id.text;
|
||||
}
|
||||
emit(id);
|
||||
seen[id.text] = id.text;
|
||||
}
|
||||
}
|
||||
write(";");
|
||||
writeLine();
|
||||
@ -7415,7 +7415,7 @@ const _super = (function (geti, seti) {
|
||||
// - import equals declarations that import external modules are not emitted
|
||||
continue;
|
||||
}
|
||||
// fall-though for import declarations that import internal modules
|
||||
// fall-though for import declarations that import internal modules
|
||||
default:
|
||||
writeLine();
|
||||
emit(statement);
|
||||
@ -8364,24 +8364,28 @@ const _super = (function (geti, seti) {
|
||||
}
|
||||
}
|
||||
|
||||
function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath}: { 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) {
|
||||
emitJavaScript(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
}
|
||||
else {
|
||||
emitSkipped = true;
|
||||
function emitFile({ jsFilePath, sourceMapFilePath, declarationFilePath }: EmitFileNames,
|
||||
sourceFiles: SourceFile[], isBundledEmit: boolean, emitOnlyDtsFiles: boolean) {
|
||||
if (!emitOnlyDtsFiles) {
|
||||
// Make sure not to write js File and source map file if any of them cannot be written
|
||||
if (!host.isEmitBlocked(jsFilePath) && !compilerOptions.noEmit) {
|
||||
emitJavaScript(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
}
|
||||
else {
|
||||
emitSkipped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (declarationFilePath) {
|
||||
emitSkipped = writeDeclarationFile(declarationFilePath, sourceFiles, isBundledEmit, host, resolver, emitterDiagnostics) || emitSkipped;
|
||||
emitSkipped = writeDeclarationFile(declarationFilePath, sourceFiles, isBundledEmit, host, resolver, emitterDiagnostics, emitOnlyDtsFiles) || emitSkipped;
|
||||
}
|
||||
|
||||
if (!emitSkipped && emittedFilesList) {
|
||||
emittedFilesList.push(jsFilePath);
|
||||
if (sourceMapFilePath) {
|
||||
emittedFilesList.push(sourceMapFilePath);
|
||||
if (!emitOnlyDtsFiles) {
|
||||
emittedFilesList.push(jsFilePath);
|
||||
if (sourceMapFilePath) {
|
||||
emittedFilesList.push(sourceMapFilePath);
|
||||
}
|
||||
}
|
||||
if (declarationFilePath) {
|
||||
emittedFilesList.push(declarationFilePath);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/// <reference path="utilities.ts"/>
|
||||
/// <reference path="utilities.ts"/>
|
||||
/// <reference path="scanner.ts"/>
|
||||
|
||||
namespace ts {
|
||||
|
||||
@ -774,15 +774,15 @@ namespace ts {
|
||||
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) {
|
||||
@ -827,7 +827,8 @@ namespace ts {
|
||||
const emitResult = emitFiles(
|
||||
emitResolver,
|
||||
getEmitHost(writeFileCallback),
|
||||
sourceFile);
|
||||
sourceFile,
|
||||
emitOnlyDtsFiles);
|
||||
|
||||
performance.mark("afterEmit");
|
||||
performance.measure("Emit", "beforeEmit", "afterEmit");
|
||||
|
||||
@ -19,6 +19,7 @@ namespace ts {
|
||||
remove(fileName: Path): void;
|
||||
|
||||
forEachValue(f: (key: Path, v: T) => void): void;
|
||||
getKeys(): Path[];
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
@ -1755,7 +1756,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[];
|
||||
@ -2736,6 +2737,7 @@ namespace ts {
|
||||
raw?: any;
|
||||
errors: Diagnostic[];
|
||||
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
export const enum WatchDirectoryFlags {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/// <reference path="sys.ts" />
|
||||
/// <reference path="sys.ts" />
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
@ -2218,12 +2218,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";
|
||||
}
|
||||
const path = outputDir
|
||||
? getSourceFilePathInNewDir(sourceFile, host, outputDir)
|
||||
: sourceFile.fileName;
|
||||
return removeFileExtension(path) + ".d.ts";
|
||||
}
|
||||
|
||||
export interface EmitFileNames {
|
||||
@ -2233,8 +2231,9 @@ namespace ts {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -2267,12 +2266,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) {
|
||||
@ -2290,7 +2290,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -698,7 +698,8 @@ namespace Harness.LanguageService {
|
||||
/*useOneInferredProject*/ false,
|
||||
/*typingsInstaller*/ undefined,
|
||||
Utils.byteLength,
|
||||
process.hrtime, serverHost);
|
||||
process.hrtime, serverHost,
|
||||
/*canUseEvents*/ true);
|
||||
|
||||
// Fake the connection between the client and the server
|
||||
serverHost.writeMessage = client.onMessage.bind(client);
|
||||
|
||||
@ -44,7 +44,7 @@ namespace ts.server {
|
||||
let lastSent: protocol.Message;
|
||||
|
||||
beforeEach(() => {
|
||||
session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, 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;
|
||||
};
|
||||
@ -269,7 +269,7 @@ namespace ts.server {
|
||||
lastSent: protocol.Message;
|
||||
customHandler = "testhandler";
|
||||
constructor() {
|
||||
super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, 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 };
|
||||
});
|
||||
@ -327,7 +327,7 @@ namespace ts.server {
|
||||
class InProcSession extends Session {
|
||||
private queue: protocol.Request[] = [];
|
||||
constructor(private client: InProcClient) {
|
||||
super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
368
src/server/builder.ts
Normal file
368
src/server/builder.ts
Normal file
@ -0,0 +1,368 @@
|
||||
/// <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");
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
for (const outputFile of outputFiles) {
|
||||
writeFile(outputFile.name, 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);
|
||||
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 [scriptInfo.fileName];
|
||||
}
|
||||
return this.project.getFileNamesWithoutDefaultLib();
|
||||
}
|
||||
return [scriptInfo.fileName];
|
||||
}
|
||||
}
|
||||
|
||||
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 fileInfo = this.getFileInfo(scriptInfo.path);
|
||||
if (!fileInfo || !fileInfo.updateShapeSignature()) {
|
||||
return [scriptInfo.fileName];
|
||||
}
|
||||
|
||||
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
|
||||
return this.project.getFileNamesWithoutDefaultLib();
|
||||
}
|
||||
|
||||
const options = this.project.getCompilerOptions();
|
||||
if (options && (options.isolatedModules || options.out || options.outFile)) {
|
||||
return [scriptInfo.fileName];
|
||||
}
|
||||
|
||||
// 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<boolean>();
|
||||
fileNameSet[scriptInfo.fileName] = true;
|
||||
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] = true;
|
||||
}
|
||||
return Object.keys(fileNameSet);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
/// <reference types="node"/>
|
||||
/// <reference types="node" />
|
||||
|
||||
|
||||
// TODO: extract services types
|
||||
|
||||
@ -30,7 +30,7 @@ namespace ts.server {
|
||||
|
||||
interface ConfigFileConversionResult {
|
||||
success: boolean;
|
||||
errors?: Diagnostic[];
|
||||
configFileErrors?: Diagnostic[];
|
||||
|
||||
projectOptions?: ProjectOptions;
|
||||
}
|
||||
@ -73,6 +73,10 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
function createFileNotFoundDiagnostic(fileName: string) {
|
||||
return createCompilerDiagnostic(Diagnostics.File_0_not_found, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: enforce invariants:
|
||||
* - script info can be never migrate to state - root file in inferred project, this is only a starting point
|
||||
@ -656,7 +660,7 @@ namespace ts.server {
|
||||
|
||||
const configObj = parseConfigFileTextToJson(configFilename, this.host.readFile(configFilename));
|
||||
if (configObj.error) {
|
||||
return { success: false, errors: [configObj.error] };
|
||||
return { success: false, configFileErrors: [configObj.error] };
|
||||
}
|
||||
|
||||
const parsedCommandLine = parseJsonConfigFileContent(
|
||||
@ -669,12 +673,12 @@ namespace ts.server {
|
||||
Debug.assert(!!parsedCommandLine.fileNames);
|
||||
|
||||
if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) {
|
||||
return { success: false, errors: parsedCommandLine.errors };
|
||||
return { success: false, configFileErrors: parsedCommandLine.errors };
|
||||
}
|
||||
|
||||
if (parsedCommandLine.fileNames.length === 0) {
|
||||
const error = createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename);
|
||||
return { success: false, errors: [error] };
|
||||
return { success: false, configFileErrors: [error] };
|
||||
}
|
||||
|
||||
const projectOptions: ProjectOptions = {
|
||||
@ -682,7 +686,8 @@ namespace ts.server {
|
||||
compilerOptions: parsedCommandLine.options,
|
||||
configHasFilesProperty: configObj.config["files"] !== undefined,
|
||||
wildcardDirectories: createMap(parsedCommandLine.wildcardDirectories),
|
||||
typingOptions: parsedCommandLine.typingOptions
|
||||
typingOptions: parsedCommandLine.typingOptions,
|
||||
compileOnSave: parsedCommandLine.compileOnSave
|
||||
};
|
||||
return { success: true, projectOptions };
|
||||
}
|
||||
@ -705,17 +710,18 @@ namespace ts.server {
|
||||
return false;
|
||||
}
|
||||
|
||||
private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], compilerOptions: CompilerOptions) {
|
||||
private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typingOptions: TypingOptions) {
|
||||
const project = new ExternalProject(
|
||||
projectFileName,
|
||||
this,
|
||||
this.documentRegistry,
|
||||
compilerOptions,
|
||||
/*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(compilerOptions, files, externalFilePropertyReader));
|
||||
options,
|
||||
/*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(options, files, externalFilePropertyReader),
|
||||
options.compileOnSave === undefined ? true : options.compileOnSave);
|
||||
|
||||
const errors = this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined);
|
||||
this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typingOptions);
|
||||
this.externalProjects.push(project);
|
||||
return { project, errors };
|
||||
return project;
|
||||
}
|
||||
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, clientFileName?: string) {
|
||||
@ -726,11 +732,11 @@ namespace ts.server {
|
||||
this.documentRegistry,
|
||||
projectOptions.configHasFilesProperty,
|
||||
projectOptions.compilerOptions,
|
||||
projectOptions.typingOptions,
|
||||
projectOptions.wildcardDirectories,
|
||||
/*languageServiceEnabled*/ !sizeLimitExceeded);
|
||||
/*languageServiceEnabled*/ !sizeLimitExceeded,
|
||||
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave);
|
||||
|
||||
const errors = this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName);
|
||||
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typingOptions);
|
||||
|
||||
project.watchConfigFile(project => this.onConfigChangedForConfiguredProject(project));
|
||||
if (!sizeLimitExceeded) {
|
||||
@ -739,7 +745,7 @@ namespace ts.server {
|
||||
project.watchWildcards((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path));
|
||||
|
||||
this.configuredProjects.push(project);
|
||||
return { project, errors };
|
||||
return project;
|
||||
}
|
||||
|
||||
private watchConfigDirectoryForProject(project: ConfiguredProject, options: ProjectOptions): void {
|
||||
@ -748,7 +754,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string): Diagnostic[] {
|
||||
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typingOptions: TypingOptions): void {
|
||||
let errors: Diagnostic[];
|
||||
for (const f of files) {
|
||||
const rootFilename = propertyReader.getFileName(f);
|
||||
@ -759,31 +765,37 @@ namespace ts.server {
|
||||
project.addRoot(info);
|
||||
}
|
||||
else {
|
||||
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.File_0_not_found, rootFilename));
|
||||
(errors || (errors = [])).push(createFileNotFoundDiagnostic(rootFilename));
|
||||
}
|
||||
}
|
||||
project.setProjectErrors(errors);
|
||||
project.setTypingOptions(typingOptions);
|
||||
project.updateGraph();
|
||||
return errors;
|
||||
}
|
||||
|
||||
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string): OpenConfigFileResult {
|
||||
const conversionResult = this.convertConfigFileContentToProjectOptions(configFileName);
|
||||
if (!conversionResult.success) {
|
||||
return { success: false, errors: conversionResult.errors };
|
||||
// open project with no files and set errors on the project
|
||||
const project = this.createAndAddConfiguredProject(configFileName, { files: [], compilerOptions: {} }, clientFileName);
|
||||
project.setProjectErrors(conversionResult.configFileErrors);
|
||||
return { success: false, errors: conversionResult.configFileErrors };
|
||||
}
|
||||
const { project, errors } = this.createAndAddConfiguredProject(configFileName, conversionResult.projectOptions, clientFileName);
|
||||
return { success: true, project, errors };
|
||||
const project = this.createAndAddConfiguredProject(configFileName, conversionResult.projectOptions, clientFileName);
|
||||
return { success: true, project, errors: project.getProjectErrors() };
|
||||
}
|
||||
|
||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions) {
|
||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
||||
const oldRootScriptInfos = project.getRootScriptInfos();
|
||||
const newRootScriptInfos: ScriptInfo[] = [];
|
||||
const newRootScriptInfoMap: NormalizedPathMap<ScriptInfo> = createNormalizedPathMap<ScriptInfo>();
|
||||
|
||||
let projectErrors: Diagnostic[];
|
||||
let rootFilesChanged = false;
|
||||
for (const f of newUncheckedFiles) {
|
||||
const newRootFile = propertyReader.getFileName(f);
|
||||
if (!this.host.fileExists(newRootFile)) {
|
||||
(projectErrors || (projectErrors = [])).push(createFileNotFoundDiagnostic(newRootFile));
|
||||
continue;
|
||||
}
|
||||
const normalizedPath = toNormalizedPath(newRootFile);
|
||||
@ -835,6 +847,10 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
project.setCompilerOptions(newOptions);
|
||||
(<ExternalProject | ConfiguredProject>project).setTypingOptions(newTypingOptions);
|
||||
project.compileOnSaveEnabled = !!compileOnSave;
|
||||
project.setProjectErrors(concatenate(configFileErrors, projectErrors));
|
||||
|
||||
project.updateGraph();
|
||||
}
|
||||
|
||||
@ -845,9 +861,11 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
const { success, projectOptions, errors } = this.convertConfigFileContentToProjectOptions(project.configFileName);
|
||||
const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.configFileName);
|
||||
if (!success) {
|
||||
return errors;
|
||||
// reset project settings to default
|
||||
this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/false, configFileErrors);
|
||||
return configFileErrors;
|
||||
}
|
||||
|
||||
if (this.exceededTotalSizeLimitForNonTsFiles(projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) {
|
||||
@ -864,7 +882,7 @@ namespace ts.server {
|
||||
project.enableLanguageService();
|
||||
}
|
||||
this.watchConfigDirectoryForProject(project, projectOptions);
|
||||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions);
|
||||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typingOptions, projectOptions.compileOnSave, configFileErrors);
|
||||
}
|
||||
}
|
||||
|
||||
@ -872,7 +890,7 @@ namespace ts.server {
|
||||
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
|
||||
const project = useExistingProject
|
||||
? this.inferredProjects[0]
|
||||
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true, this.compilerOptionsForInferredProjects);
|
||||
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true, this.compilerOptionsForInferredProjects, /*compileOnSaveEnabled*/ false);
|
||||
|
||||
project.addRoot(root);
|
||||
|
||||
@ -1047,15 +1065,15 @@ namespace ts.server {
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: protocol.ProjectFiles[]): void {
|
||||
private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void {
|
||||
for (const proj of currentProjects) {
|
||||
const knownProject = forEach(lastKnownProjectVersions, p => p.projectName === proj.getProjectName() && p);
|
||||
result.push(proj.getChangesSinceVersion(knownProject && knownProject.version));
|
||||
}
|
||||
}
|
||||
|
||||
synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): protocol.ProjectFiles[] {
|
||||
const files: protocol.ProjectFiles[] = [];
|
||||
synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): ProjectFilesWithTSDiagnostics[] {
|
||||
const files: ProjectFilesWithTSDiagnostics[] = [];
|
||||
this.collectChanges(knownProjects, this.externalProjects, files);
|
||||
this.collectChanges(knownProjects, this.configuredProjects, files);
|
||||
this.collectChanges(knownProjects, this.inferredProjects, files);
|
||||
@ -1134,7 +1152,7 @@ namespace ts.server {
|
||||
openExternalProject(proj: protocol.ExternalProject): void {
|
||||
const externalProject = this.findExternalProjectByProjectName(proj.projectFileName);
|
||||
if (externalProject) {
|
||||
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options);
|
||||
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, proj.options, proj.typingOptions, proj.options.compileOnSave, /*configFileErrors*/ undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1166,7 +1184,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options);
|
||||
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typingOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +76,10 @@ namespace ts.server {
|
||||
return this.compilationSettings;
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames() {
|
||||
return this.host.useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
getCancellationToken() {
|
||||
return this.cancellationToken;
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <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 {
|
||||
|
||||
@ -19,6 +20,16 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
const jsOrDts = [".js", ".d.ts"];
|
||||
|
||||
export function allRootFilesAreJsOrDts(project: Project): boolean {
|
||||
return project.getRootScriptInfos().every(f => fileExtensionIsAny(f.fileName, jsOrDts));
|
||||
}
|
||||
|
||||
export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles {
|
||||
projectErrors: Diagnostic[];
|
||||
}
|
||||
|
||||
export abstract class Project {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
||||
@ -26,6 +37,7 @@ namespace ts.server {
|
||||
private program: ts.Program;
|
||||
|
||||
private languageService: LanguageService;
|
||||
builder: Builder;
|
||||
/**
|
||||
* Set of files that was returned from the last call to getChangesSinceVersion.
|
||||
*/
|
||||
@ -49,13 +61,16 @@ namespace ts.server {
|
||||
|
||||
private typingFiles: TypingsArray;
|
||||
|
||||
protected projectErrors: Diagnostic[];
|
||||
|
||||
constructor(
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
private documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
public languageServiceEnabled: boolean,
|
||||
private compilerOptions: CompilerOptions) {
|
||||
private compilerOptions: CompilerOptions,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
|
||||
if (!this.compilerOptions) {
|
||||
this.compilerOptions = ts.getDefaultCompilerOptions();
|
||||
@ -73,9 +88,15 @@ namespace ts.server {
|
||||
else {
|
||||
this.disableLanguageService();
|
||||
}
|
||||
|
||||
this.builder = createBuilder(this);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
getProjectErrors() {
|
||||
return this.projectErrors;
|
||||
}
|
||||
|
||||
getLanguageService(ensureSynchronized = true): LanguageService {
|
||||
if (ensureSynchronized) {
|
||||
this.updateGraph();
|
||||
@ -83,6 +104,14 @@ namespace ts.server {
|
||||
return this.languageService;
|
||||
}
|
||||
|
||||
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return [];
|
||||
}
|
||||
this.updateGraph();
|
||||
return this.builder.getFilesAffectedBy(scriptInfo);
|
||||
}
|
||||
|
||||
getProjectVersion() {
|
||||
return this.projectStateVersion.toString();
|
||||
}
|
||||
@ -103,6 +132,14 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
abstract getProjectName(): string;
|
||||
abstract getTypingOptions(): TypingOptions;
|
||||
|
||||
getSourceFile(path: Path) {
|
||||
if (!this.program) {
|
||||
return undefined;
|
||||
}
|
||||
return this.program.getSourceFileByPath(path);
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.program) {
|
||||
@ -157,6 +194,17 @@ namespace ts.server {
|
||||
return this.rootFiles;
|
||||
}
|
||||
|
||||
getScriptInfos() {
|
||||
return map(this.program.getSourceFiles(), sourceFile => this.getScriptInfoLSHost(sourceFile.path));
|
||||
}
|
||||
|
||||
getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
||||
}
|
||||
|
||||
getFileNames() {
|
||||
if (!this.program) {
|
||||
return [];
|
||||
@ -177,6 +225,14 @@ namespace ts.server {
|
||||
return sourceFiles.map(sourceFile => asNormalizedPath(sourceFile.fileName));
|
||||
}
|
||||
|
||||
getFileNamesWithoutDefaultLib() {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return this.getRootFiles();
|
||||
}
|
||||
const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions);
|
||||
return filter(this.getFileNames(), file => getBaseFileName(file) !== defaultLibraryFileName);
|
||||
}
|
||||
|
||||
containsScriptInfo(info: ScriptInfo): boolean {
|
||||
return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined);
|
||||
}
|
||||
@ -269,6 +325,7 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.builder.onProjectUpdateGraph();
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
@ -282,7 +339,9 @@ namespace ts.server {
|
||||
|
||||
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
|
||||
Debug.assert(!scriptInfo || scriptInfo.isAttached(this));
|
||||
if (scriptInfo && !scriptInfo.isAttached(this)) {
|
||||
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
|
||||
}
|
||||
return scriptInfo;
|
||||
}
|
||||
|
||||
@ -324,7 +383,7 @@ namespace ts.server {
|
||||
return false;
|
||||
}
|
||||
|
||||
getChangesSinceVersion(lastKnownVersion?: number): protocol.ProjectFiles {
|
||||
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
|
||||
this.updateGraph();
|
||||
|
||||
const info = {
|
||||
@ -337,7 +396,7 @@ namespace ts.server {
|
||||
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 };
|
||||
return { info, projectErrors: this.projectErrors };
|
||||
}
|
||||
// compute and return the difference
|
||||
const lastReportedFileNames = this.lastReportedFileNames;
|
||||
@ -359,17 +418,70 @@ namespace ts.server {
|
||||
|
||||
this.lastReportedFileNames = currentFiles;
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
return { info, changes: { added, removed } };
|
||||
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 };
|
||||
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) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return map(Object.keys(referencedFiles), key => <Path>key);
|
||||
}
|
||||
|
||||
// remove a root file from project
|
||||
private removeRootFileIfNecessary(info: ScriptInfo): void {
|
||||
if (this.isRoot(info)) {
|
||||
@ -391,13 +503,14 @@ namespace ts.server {
|
||||
// Used to keep track of what directories are watched for this project
|
||||
directoriesWatchedForTsconfig: string[] = [];
|
||||
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean, compilerOptions: CompilerOptions) {
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean, compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.Inferred,
|
||||
projectService,
|
||||
documentRegistry,
|
||||
/*files*/ undefined,
|
||||
languageServiceEnabled,
|
||||
compilerOptions);
|
||||
compilerOptions,
|
||||
compileOnSaveEnabled);
|
||||
|
||||
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId);
|
||||
InferredProject.NextId++;
|
||||
@ -414,9 +527,18 @@ namespace ts.server {
|
||||
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>;
|
||||
@ -428,10 +550,18 @@ namespace ts.server {
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
compilerOptions: CompilerOptions,
|
||||
private typingOptions: TypingOptions,
|
||||
private wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
languageServiceEnabled: boolean) {
|
||||
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions);
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
}
|
||||
|
||||
setProjectErrors(projectErrors: Diagnostic[]) {
|
||||
this.projectErrors = projectErrors;
|
||||
}
|
||||
|
||||
setTypingOptions(newTypingOptions: TypingOptions): void {
|
||||
this.typingOptions = newTypingOptions;
|
||||
}
|
||||
|
||||
getTypingOptions() {
|
||||
@ -508,12 +638,46 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export class ExternalProject extends Project {
|
||||
private typingOptions: TypingOptions;
|
||||
constructor(readonly externalProjectName: string,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
languageServiceEnabled: boolean) {
|
||||
super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions);
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean) {
|
||||
super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
35
src/server/protocol.d.ts
vendored
35
src/server/protocol.d.ts
vendored
@ -498,7 +498,16 @@ declare namespace ts.server.protocol {
|
||||
export interface ExternalProject {
|
||||
projectFileName: string;
|
||||
rootFiles: ExternalFile[];
|
||||
options: CompilerOptions;
|
||||
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 {
|
||||
@ -526,6 +535,10 @@ declare namespace ts.server.protocol {
|
||||
changes?: ProjectChanges;
|
||||
}
|
||||
|
||||
export interface ProjectFilesWithDiagnostics extends ProjectFiles {
|
||||
projectErrors: DiagnosticWithLinePosition[];
|
||||
}
|
||||
|
||||
export interface ChangedOpenFile {
|
||||
fileName: string;
|
||||
changes: ts.TextChange[];
|
||||
@ -720,6 +733,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
|
||||
|
||||
@ -232,8 +232,22 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
class IOSession extends Session {
|
||||
constructor(host: ServerHost, cancellationToken: HostCancellationToken, eventPort: number, useSingleInferredProject: boolean, logger: server.Logger) {
|
||||
super(host, cancellationToken, useSingleInferredProject, new NodeTypingsInstaller(logger, eventPort, host.newLine), Buffer.byteLength, process.hrtime, logger);
|
||||
constructor(
|
||||
host: ServerHost,
|
||||
cancellationToken: HostCancellationToken,
|
||||
installerEventPort: number,
|
||||
canUseEvents: boolean,
|
||||
useSingleInferredProject: boolean,
|
||||
logger: server.Logger) {
|
||||
super(
|
||||
host,
|
||||
cancellationToken,
|
||||
useSingleInferredProject,
|
||||
new NodeTypingsInstaller(logger, installerEventPort, host.newLine),
|
||||
Buffer.byteLength,
|
||||
process.hrtime,
|
||||
logger,
|
||||
canUseEvents);
|
||||
}
|
||||
|
||||
exit() {
|
||||
@ -477,7 +491,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
|
||||
const ioSession = new IOSession(sys, cancellationToken, eventPort, useSingleInferredProject, logger);
|
||||
const ioSession = new IOSession(sys, cancellationToken, eventPort, /*canUseEvents*/ eventPort === undefined, useSingleInferredProject, logger);
|
||||
process.on("uncaughtException", function (err: Error) {
|
||||
ioSession.logError(err, "unknown");
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/// <reference path="..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="..\compiler\commandLineParser.ts" />
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="protocol.d.ts" />
|
||||
/// <reference path="editorServices.ts" />
|
||||
@ -79,6 +79,8 @@ namespace ts.server {
|
||||
export const Completions = "completions";
|
||||
export const CompletionsFull = "completions-full";
|
||||
export const CompletionDetails = "completionEntryDetails";
|
||||
export const CompileOnSaveAffectedFileList = "compileOnSaveAffectedFileList";
|
||||
export const CompileOnSaveEmitFile = "compileOnSaveEmitFile";
|
||||
export const Configure = "configure";
|
||||
export const Definition = "definition";
|
||||
export const DefinitionFull = "definition-full";
|
||||
@ -158,11 +160,14 @@ namespace ts.server {
|
||||
protected readonly typingsInstaller: ITypingsInstaller,
|
||||
private byteLength: (buf: string, encoding?: string) => number,
|
||||
private hrtime: (start?: number[]) => number[],
|
||||
protected logger: Logger) {
|
||||
this.projectService =
|
||||
new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, (eventName, project, fileName) => {
|
||||
this.handleEvent(eventName, project, fileName);
|
||||
});
|
||||
protected logger: Logger,
|
||||
protected readonly canUseEvents: boolean) {
|
||||
|
||||
const eventHandler: ProjectServiceEventHandler = canUseEvents
|
||||
? (eventName, project, fileName) => this.handleEvent(eventName, project, fileName)
|
||||
: undefined;
|
||||
|
||||
this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler);
|
||||
this.gcTimer = new GcTimer(host, /*delay*/ 15000, logger);
|
||||
}
|
||||
|
||||
@ -186,6 +191,12 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
public send(msg: protocol.Message) {
|
||||
if (msg.type === "event" && !this.canUseEvents) {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
|
||||
}
|
||||
|
||||
@ -930,6 +941,32 @@ namespace ts.server {
|
||||
}, []);
|
||||
}
|
||||
|
||||
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): protocol.CompileOnSaveAffectedFileListSingleProject[] {
|
||||
const info = this.projectService.getScriptInfo(args.file);
|
||||
const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = [];
|
||||
|
||||
// if specified a project, we only return affected file list in this project
|
||||
const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects;
|
||||
for (const project of projectsToSearch) {
|
||||
if (project.compileOnSaveEnabled) {
|
||||
result.push({
|
||||
projectFileName: project.getProjectName(),
|
||||
fileNames: project.getCompileOnSaveAffectedFileList(info)
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
if (!project) {
|
||||
Errors.ThrowNoProject();
|
||||
}
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
return project.builder.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
|
||||
}
|
||||
|
||||
private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems {
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
@ -1224,7 +1261,21 @@ namespace ts.server {
|
||||
},
|
||||
[CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => {
|
||||
const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects);
|
||||
return this.requiredResponse(result);
|
||||
if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) {
|
||||
return this.requiredResponse(result);
|
||||
}
|
||||
const converted = map(result, p => {
|
||||
if (!p.projectErrors || p.projectErrors.length === 0) {
|
||||
return p;
|
||||
}
|
||||
return {
|
||||
info: p.info,
|
||||
changes: p.changes,
|
||||
files: p.files,
|
||||
projectErrors: this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined)
|
||||
};
|
||||
});
|
||||
return this.requiredResponse(converted);
|
||||
},
|
||||
[CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => {
|
||||
this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles, request.arguments.closedFiles);
|
||||
@ -1331,6 +1382,12 @@ namespace ts.server {
|
||||
[CommandNames.CompletionDetails]: (request: protocol.CompletionDetailsRequest) => {
|
||||
return this.requiredResponse(this.getCompletionEntryDetails(request.arguments));
|
||||
},
|
||||
[CommandNames.CompileOnSaveAffectedFileList]: (request: protocol.CompileOnSaveAffectedFileListRequest) => {
|
||||
return this.requiredResponse(this.getCompileOnSaveAffectedFileList(request.arguments));
|
||||
},
|
||||
[CommandNames.CompileOnSaveEmitFile]: (request: protocol.CompileOnSaveEmitFileRequest) => {
|
||||
return this.requiredResponse(this.emitFile(request.arguments));
|
||||
},
|
||||
[CommandNames.SignatureHelp]: (request: protocol.SignatureHelpRequest) => {
|
||||
return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ true));
|
||||
},
|
||||
|
||||
@ -20,20 +20,6 @@ namespace ts.server {
|
||||
poisoned: boolean;
|
||||
}
|
||||
|
||||
const emptyArray: any[] = [];
|
||||
const jsOrDts = [".js", ".jsx", ".d.ts"];
|
||||
|
||||
function getTypingOptionsForProjects(proj: Project): TypingOptions {
|
||||
if (proj.projectKind === ProjectKind.Configured) {
|
||||
return (<ConfiguredProject>proj).getTypingOptions();
|
||||
}
|
||||
|
||||
const enableAutoDiscovery = proj.getFileNames().every(f => fileExtensionIsAny(f, jsOrDts));
|
||||
|
||||
// TODO: add .d.ts files to excludes
|
||||
return { enableAutoDiscovery, include: emptyArray, exclude: emptyArray };
|
||||
}
|
||||
|
||||
function setIsEqualTo(arr1: string[], arr2: string[]): boolean {
|
||||
if (arr1 === arr2) {
|
||||
return true;
|
||||
@ -89,9 +75,9 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getTypingsForProject(project: Project): TypingsArray {
|
||||
const typingOptions = getTypingOptionsForProjects(project);
|
||||
const typingOptions = project.getTypingOptions();
|
||||
|
||||
if (!typingOptions.enableAutoDiscovery) {
|
||||
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
|
||||
return <any>emptyArray;
|
||||
}
|
||||
|
||||
@ -113,7 +99,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
invalidateCachedTypingsForProject(project: Project) {
|
||||
const typingOptions = getTypingOptionsForProjects(project);
|
||||
const typingOptions = project.getTypingOptions();
|
||||
if (!typingOptions.enableAutoDiscovery) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/// <reference path="types.d.ts" />
|
||||
/// <reference path="types.d.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export enum LogLevel {
|
||||
@ -8,6 +8,8 @@ namespace ts.server {
|
||||
verbose
|
||||
}
|
||||
|
||||
export const emptyArray: ReadonlyArray<any> = [];
|
||||
|
||||
export interface Logger {
|
||||
close(): void;
|
||||
hasLevel(level: LogLevel): boolean;
|
||||
@ -62,6 +64,9 @@ namespace ts.server {
|
||||
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 {
|
||||
@ -218,6 +223,7 @@ namespace ts.server {
|
||||
wildcardDirectories?: Map<WatchDirectoryFlags>;
|
||||
compilerOptions?: CompilerOptions;
|
||||
typingOptions?: TypingOptions;
|
||||
compileOnSave?: boolean;
|
||||
}
|
||||
|
||||
export function isInferredProjectName(name: string) {
|
||||
|
||||
@ -29,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
|
||||
@ -63,7 +65,7 @@ namespace ts.JsTyping {
|
||||
|
||||
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[] = [];
|
||||
@ -163,10 +165,8 @@ 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));
|
||||
}
|
||||
|
||||
|
||||
@ -1253,7 +1253,7 @@ namespace ts {
|
||||
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
|
||||
getEmitOutput(fileName: string): EmitOutput;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
|
||||
getProgram(): Program;
|
||||
|
||||
@ -3053,7 +3053,7 @@ namespace ts {
|
||||
let program: Program;
|
||||
let lastProjectVersion: string;
|
||||
|
||||
const useCaseSensitivefileNames = false;
|
||||
const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames();
|
||||
const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());
|
||||
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
@ -7069,7 +7069,7 @@ namespace ts {
|
||||
return ts.NavigateTo.getNavigateToItems(program, checker, cancellationToken, searchValue, maxResultCount);
|
||||
}
|
||||
|
||||
function getEmitOutput(fileName: string): EmitOutput {
|
||||
function getEmitOutput(fileName: string, emitDeclarationsOnly?: boolean): EmitOutput {
|
||||
synchronizeHostData();
|
||||
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
@ -7083,7 +7083,7 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken);
|
||||
const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitDeclarationsOnly);
|
||||
|
||||
return {
|
||||
outputFiles,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user