Merge branch 'master' into map4

This commit is contained in:
Andy Hanson
2016-10-12 08:50:49 -07:00
29 changed files with 1201 additions and 95 deletions
+3 -3
View File
@@ -2223,9 +2223,9 @@ namespace ts {
if (currentFlow) {
node.flowNode = currentFlow;
}
checkStrictModeFunctionName(<FunctionExpression>node);
const bindingName = (<FunctionExpression>node).name ? (<FunctionExpression>node).name.text : "__function";
return bindAnonymousDeclaration(<FunctionExpression>node, SymbolFlags.Function, bindingName);
checkStrictModeFunctionName(node);
const bindingName = node.name ? node.name.text : "__function";
return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName);
}
function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
+1 -1
View File
@@ -972,7 +972,7 @@ namespace ts {
basePath: string, errors: Diagnostic[], configFileName?: string): CompilerOptions {
const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json"
? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true }
? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true }
: {};
convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_compiler_option_0, errors);
return options;
+28 -1
View File
@@ -3073,7 +3073,6 @@
"category": "Error",
"code": 17010
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",
"code": 18000
@@ -3081,5 +3080,33 @@
"The path in an 'extends' options must be relative or rooted.": {
"category": "Error",
"code": 18001
},
"Add missing 'super()' call.": {
"category": "Message",
"code": 90001
},
"Make 'super()' call the first statement in the constructor.": {
"category": "Message",
"code": 90002
},
"Change 'extends' to 'implements'": {
"category": "Message",
"code": 90003
},
"Remove unused identifiers": {
"category": "Message",
"code": 90004
},
"Implement interface on reference": {
"category": "Message",
"code": 90005
},
"Implement interface on class": {
"category": "Message",
"code": 90006
},
"Implement inherited abstract class": {
"category": "Message",
"code": 90007
}
}
+1 -1
View File
@@ -5352,7 +5352,7 @@ namespace ts {
parseExpected(SyntaxKind.EqualsToken);
node.type = parseType();
parseSemicolon();
return finishNode(node);
return addJSDocComment(finishNode(node));
}
// In an ambient declaration, the grammar only allows integer literals as initializers.
+2 -3
View File
@@ -358,7 +358,7 @@ namespace ts {
// load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders
const typeReferences: string[] = getAutomaticTypeDirectiveNames(options, host);
if (typeReferences) {
if (typeReferences.length) {
// This containingFilename needs to match with the one used in managed-side
const containingFilename = combinePaths(host.getCurrentDirectory(), "__inferred type names__.ts");
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename);
@@ -1307,7 +1307,6 @@ namespace ts {
for (let i = 0; i < moduleNames.length; i++) {
const resolution = resolutions[i];
setResolvedModule(file, moduleNames[i], resolution);
const resolvedPath = resolution ? toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName) : undefined;
// add file to program only if:
// - resolution was successful
@@ -1329,7 +1328,7 @@ namespace ts {
}
else if (shouldAddFile) {
findSourceFile(resolution.resolvedFileName,
resolvedPath,
toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName),
/*isDefaultLib*/ false, /*isReference*/ false,
file,
skipTrivia(file.text, file.imports[i].pos),
+68 -4
View File
@@ -1,4 +1,4 @@
//
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -427,7 +427,7 @@ namespace FourSlash {
if (exists !== negative) {
this.printErrorLog(negative, this.getAllDiagnostics());
throw new Error("Failure between markers: " + startMarkerName + ", " + endMarkerName);
throw new Error(`Failure between markers: '${startMarkerName}', '${endMarkerName}'`);
}
}
@@ -740,7 +740,6 @@ namespace FourSlash {
}
}
public verifyCompletionListAllowsNewIdentifier(negative: boolean) {
const completions = this.getCompletionListAtCaret();
@@ -1610,7 +1609,7 @@ namespace FourSlash {
if (isFormattingEdit) {
const newContent = this.getFileContent(fileName);
if (newContent.replace(/\s/g, "") !== oldContent.replace(/\s/g, "")) {
if (this.removeWhitespace(newContent) !== this.removeWhitespace(oldContent)) {
this.raiseError("Formatting operation destroyed non-whitespace content");
}
}
@@ -1676,6 +1675,10 @@ namespace FourSlash {
}
}
private removeWhitespace(text: string): string {
return text.replace(/\s/g, "");
}
public goToBOF() {
this.goToPosition(0);
}
@@ -2041,6 +2044,47 @@ namespace FourSlash {
}
}
private getCodeFixes(errorCode?: number) {
const fileName = this.activeFile.fileName;
const diagnostics = this.getDiagnostics(fileName);
if (diagnostics.length === 0) {
this.raiseError("Errors expected.");
}
if (diagnostics.length > 1 && errorCode !== undefined) {
this.raiseError("When there's more than one error, you must specify the errror to fix.");
}
const diagnostic = !errorCode ? diagnostics[0] : ts.find(diagnostics, d => d.code == errorCode);
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code]);
}
public verifyCodeFixAtPosition(expectedText: string, errorCode?: number) {
const ranges = this.getRanges();
if (ranges.length == 0) {
this.raiseError("At least one range should be specified in the testfile.");
}
const actual = this.getCodeFixes(errorCode);
if (!actual || actual.length == 0) {
this.raiseError("No codefixes returned.");
}
if (actual.length > 1) {
this.raiseError("More than 1 codefix returned.");
}
this.applyEdits(actual[0].changes[0].fileName, actual[0].changes[0].textChanges, /*isFormattingEdit*/ false);
const actualText = this.rangeText(ranges[0]);
if (this.removeWhitespace(actualText) !== this.removeWhitespace(expectedText)) {
this.raiseError(`Actual text doesn't match expected text. Actual: '${actualText}' Expected: '${expectedText}'`);
}
}
public verifyDocCommentTemplate(expected?: ts.TextInsertion) {
const name = "verifyDocCommentTemplate";
const actual = this.languageService.getDocCommentTemplateAtPosition(this.activeFile.fileName, this.currentCaretPosition);
@@ -2312,6 +2356,18 @@ namespace FourSlash {
}
}
public verifyCodeFixAvailable(negative: boolean, errorCode?: number) {
const fixes = this.getCodeFixes(errorCode);
if (negative && fixes && fixes.length > 0) {
this.raiseError(`verifyCodeFixAvailable failed - expected no fixes, actual: ${fixes.length}`);
}
if (!negative && (fixes === undefined || fixes.length === 0)) {
this.raiseError(`verifyCodeFixAvailable failed - expected code fixes, actual: 0`);
}
}
// Get the text of the entire line the caret is currently at
private getCurrentLineContent() {
const text = this.getFileContent(this.activeFile.fileName);
@@ -3099,6 +3155,10 @@ namespace FourSlashInterface {
public isValidBraceCompletionAtPosition(openingBrace: string) {
this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace);
}
public codeFixAvailable(errorCode?: number) {
this.state.verifyCodeFixAvailable(this.negative, errorCode);
}
}
export class Verify extends VerifyNegatable {
@@ -3278,6 +3338,10 @@ namespace FourSlashInterface {
this.DocCommentTemplate(/*expectedText*/ undefined, /*expectedOffset*/ undefined, /*empty*/ true);
}
public codeFixAtPosition(expectedText: string, errorCode?: number): void {
this.state.verifyCodeFixAtPosition(expectedText, errorCode);
}
public navigationBar(json: any) {
this.state.verifyNavigationBar(json);
}
+3
View File
@@ -486,6 +486,9 @@ namespace Harness.LanguageService {
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace));
}
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
@@ -405,6 +405,7 @@ namespace ts {
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
@@ -433,6 +434,7 @@ namespace ts {
allowJs: false,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
@@ -456,7 +458,8 @@ namespace ts {
{
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true
allowSyntheticDefaultImports: true,
skipLibCheck: true
},
errors: [{
file: undefined,
@@ -477,7 +480,8 @@ namespace ts {
{
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true
allowSyntheticDefaultImports: true,
skipLibCheck: true
},
errors: <Diagnostic[]>[]
}
+277 -11
View File
@@ -1,8 +1,10 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\harness.ts" />
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
namespace ts.projectSystem {
import TI = server.typingsInstaller;
import protocol = server.protocol;
import CommandNames = server.CommandNames;
const safeList = {
path: <Path>"/safeList.json",
@@ -128,7 +130,7 @@ namespace ts.projectSystem {
return combinePaths(getDirectoryPath(libFile.path), "tsc.js");
}
export function toExternalFile(fileName: string): server.protocol.ExternalFile {
export function toExternalFile(fileName: string): protocol.ExternalFile {
return { fileName };
}
@@ -136,6 +138,19 @@ namespace ts.projectSystem {
return map(fileNames, toExternalFile);
}
export class TestServerEventManager {
private events: server.ProjectServiceEvent[] = [];
handler: server.ProjectServiceEventHandler = (event: server.ProjectServiceEvent) => {
this.events.push(event);
}
checkEventCountOfType(eventType: "context" | "configFileDiag", expectedCount: number) {
const eventsOfType = filter(this.events, e => e.eventName === eventType);
assert.equal(eventsOfType.length, expectedCount, `The actual event counts of type ${eventType} is ${eventsOfType.length}, while expected ${expectedCount}`);
}
}
export interface TestServerHostCreationParameters {
useCaseSensitiveFileNames?: boolean;
executingFilePath?: string;
@@ -159,11 +174,11 @@ namespace ts.projectSystem {
return host;
}
export function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller) {
export function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller, projectServiceEventHandler?: server.ProjectServiceEventHandler) {
if (typingsInstaller === undefined) {
typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
}
return new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false);
return new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ projectServiceEventHandler !== undefined, projectServiceEventHandler);
}
export interface CreateProjectServiceParameters {
@@ -505,7 +520,7 @@ namespace ts.projectSystem {
}
export function makeSessionRequest<T>(command: string, args: T) {
const newRequest: server.protocol.Request = {
const newRequest: protocol.Request = {
seq: 0,
type: "request",
command,
@@ -516,7 +531,7 @@ namespace ts.projectSystem {
export function openFilesForSession(files: FileOrFolder[], session: server.Session) {
for (const file of files) {
const request = makeSessionRequest<server.protocol.OpenRequestArgs>(server.CommandNames.Open, { file: file.path });
const request = makeSessionRequest<protocol.OpenRequestArgs>(CommandNames.Open, { file: file.path });
session.executeCommand(request);
}
}
@@ -1729,7 +1744,7 @@ namespace ts.projectSystem {
});
describe("navigate-to for javascript project", () => {
function containsNavToItem(items: server.protocol.NavtoItem[], itemName: string, itemKind: string) {
function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) {
return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined;
}
@@ -1747,12 +1762,12 @@ namespace ts.projectSystem {
openFilesForSession([file1], session);
// Try to find some interface type defined in lib.d.ts
const libTypeNavToRequest = makeSessionRequest<server.protocol.NavtoRequestArgs>(server.CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path });
const items: server.protocol.NavtoItem[] = session.executeCommand(libTypeNavToRequest).response;
const libTypeNavToRequest = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path });
const items: protocol.NavtoItem[] = session.executeCommand(libTypeNavToRequest).response;
assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`);
const localFunctionNavToRequst = makeSessionRequest<server.protocol.NavtoRequestArgs>(server.CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path });
const items2: server.protocol.NavtoItem[] = session.executeCommand(localFunctionNavToRequst).response;
const localFunctionNavToRequst = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path });
const items2: protocol.NavtoItem[] = session.executeCommand(localFunctionNavToRequst).response;
assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`);
});
});
@@ -2112,4 +2127,255 @@ namespace ts.projectSystem {
projectService.inferredProjects[0].getLanguageService().getProgram();
});
});
describe("rename a module file and rename back", () => {
it("should restore the states for inferred projects", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: "export function bar() { };"
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
};
const host = createServerHost([moduleFile, file1]);
const session = createSession(host);
openFilesForSession([file1], session);
const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
server.CommandNames.SemanticDiagnosticsSync,
{ file: file1.path }
);
let diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 0);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileOldPath);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 1);
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileNewPath);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
// Make a change to trigger the program rebuild
const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(
server.CommandNames.Change,
{ file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" }
);
session.executeCommand(changeRequest);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 0);
});
it("should restore the states for configured projects", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: "export function bar() { };"
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createServerHost([moduleFile, file1, configFile]);
const session = createSession(host);
openFilesForSession([file1], session);
const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
server.CommandNames.SemanticDiagnosticsSync,
{ file: file1.path }
);
let diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 0);
const moduleFileOldPath = moduleFile.path;
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileOldPath);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 1);
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileNewPath);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 0);
});
});
describe("add the missing module file for inferred project", () => {
it("should remove the `module not found` error", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: "export function bar() { };"
};
const file1 = {
path: "/a/b/file1.ts",
content: "import * as T from './moduleFile'; T.bar();"
};
const host = createServerHost([file1]);
const session = createSession(host);
openFilesForSession([file1], session);
const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>(
server.CommandNames.SemanticDiagnosticsSync,
{ file: file1.path }
);
let diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 1);
host.reloadFS([file1, moduleFile]);
host.triggerDirectoryWatcherCallback(getDirectoryPath(file1.path), moduleFile.path);
host.runQueuedTimeoutCallbacks();
// Make a change to trigger the program rebuild
const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(
server.CommandNames.Change,
{ file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" }
);
session.executeCommand(changeRequest);
// Recheck
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
assert.equal(diags.length, 0);
});
});
describe("Configure file diagnostics events", () => {
it("are generated when the config file has errors", () => {
const serverEventManager = new TestServerEventManager();
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {
"foo": "bar",
"allowJS": true
}
}`
};
const host = createServerHost([file, configFile]);
const session = createSession(host, /*typingsInstaller*/ undefined, serverEventManager.handler);
openFilesForSession([file], session);
serverEventManager.checkEventCountOfType("configFileDiag", 1);
});
it("are generated when the config file doesn't have errors", () => {
const serverEventManager = new TestServerEventManager();
const file = {
path: "/a/b/app.ts",
content: "let x = 10"
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compilerOptions": {}
}`
};
const host = createServerHost([file, configFile]);
const session = createSession(host, /*typingsInstaller*/ undefined, serverEventManager.handler);
openFilesForSession([file], session);
serverEventManager.checkEventCountOfType("configFileDiag", 1);
});
});
describe("skipLibCheck", () => {
it("should be turned on for js-only inferred projects", () => {
const file1 = {
path: "/a/b/file1.js",
content: `
/// <reference path="file2.d.ts" />
var x = 1;`
};
const file2 = {
path: "/a/b/file2.d.ts",
content: `
interface T {
name: string;
};
interface T {
name: number;
};`
};
const host = createServerHost([file1, file2]);
const session = createSession(host);
openFilesForSession([file1, file2], session);
const file2GetErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
CommandNames.SemanticDiagnosticsSync,
{ file: file2.path }
);
let errorResult = <protocol.Diagnostic[]>session.executeCommand(file2GetErrRequest).response;
assert.isTrue(errorResult.length === 0);
const closeFileRequest = makeSessionRequest<protocol.FileRequestArgs>(CommandNames.Close, { file: file1.path });
session.executeCommand(closeFileRequest);
errorResult = <protocol.Diagnostic[]>session.executeCommand(file2GetErrRequest).response;
assert.isTrue(errorResult.length !== 0);
openFilesForSession([file1], session);
errorResult = <protocol.Diagnostic[]>session.executeCommand(file2GetErrRequest).response;
assert.isTrue(errorResult.length === 0);
});
it("should be turned on for js-only external projects", () => {
const jsFile = {
path: "/a/b/file1.js",
content: "let x =1;"
};
const dTsFile = {
path: "/a/b/file2.d.ts",
content: `
interface T {
name: string;
};
interface T {
name: number;
};`
};
const host = createServerHost([jsFile, dTsFile]);
const session = createSession(host);
const openExternalProjectRequest = makeSessionRequest<protocol.OpenExternalProjectArgs>(
CommandNames.OpenExternalProject,
{
projectFileName: "project1",
rootFiles: toExternalFiles([jsFile.path, dTsFile.path]),
options: {}
}
);
session.executeCommand(openExternalProjectRequest);
const dTsFileGetErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
CommandNames.SemanticDiagnosticsSync,
{ file: dTsFile.path }
);
const errorResult = <protocol.Diagnostic[]>session.executeCommand(dTsFileGetErrRequest).response;
assert.isTrue(errorResult.length === 0);
});
});
}
+68 -2
View File
@@ -425,11 +425,35 @@ namespace ts.server {
}
getSyntacticDiagnostics(fileName: string): Diagnostic[] {
throw new Error("Not Implemented Yet.");
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName };
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest>(CommandNames.SyntacticDiagnosticsSync, args);
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse>(request);
return (<protocol.Diagnostic[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
}
getSemanticDiagnostics(fileName: string): Diagnostic[] {
throw new Error("Not Implemented Yet.");
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName };
const request = this.processRequest<protocol.SemanticDiagnosticsSyncRequest>(CommandNames.SemanticDiagnosticsSync, args);
const response = this.processResponse<protocol.SemanticDiagnosticsSyncResponse>(request);
return (<protocol.Diagnostic[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
}
convertDiagnostic(entry: protocol.Diagnostic, fileName: string): Diagnostic {
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
file: undefined,
start: start,
length: end - start,
messageText: entry.text,
category: undefined,
code: entry.code
};
}
getCompilerOptionsDiagnostics(): Diagnostic[] {
@@ -630,6 +654,48 @@ namespace ts.server {
throw new Error("Not Implemented Yet.");
}
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
const startLineOffset = this.positionToOneBasedLineOffset(fileName, start);
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
const args: protocol.CodeFixRequestArgs = {
file: fileName,
startLine: startLineOffset.line,
startOffset: startLineOffset.offset,
endLine: endLineOffset.line,
endOffset: endLineOffset.offset,
errorCodes: errorCodes,
};
const request = this.processRequest<protocol.CodeFixRequest>(CommandNames.GetCodeFixes, args);
const response = this.processResponse<protocol.CodeFixResponse>(request);
return response.body.map(entry => this.convertCodeActions(entry, fileName));
}
convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction {
return {
description: entry.description,
changes: entry.changes.map(change => ({
fileName: change.fileName,
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName))
}))
};
}
convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange {
const start = this.lineOffsetToPosition(fileName, change.start);
const end = this.lineOffsetToPosition(fileName, change.end);
return {
span: {
start: start,
length: end - start
},
newText: change.newText ? change.newText : ""
};
}
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
+12 -6
View File
@@ -181,6 +181,8 @@ namespace ts.server {
private toCanonicalFileName: (f: string) => string;
public lastDeletedFile: ScriptInfo;
constructor(public readonly host: ServerHost,
public readonly logger: Logger,
public readonly cancellationToken: HostCancellationToken,
@@ -273,7 +275,7 @@ namespace ts.server {
else {
projectsToUpdate = [];
for (const f of this.changedFiles) {
projectsToUpdate = projectsToUpdate.concat(f.containingProjects);
projectsToUpdate = projectsToUpdate.concat(f.containingProjects);
}
}
this.updateProjectGraphs(projectsToUpdate);
@@ -343,6 +345,7 @@ namespace ts.server {
if (!info.isOpen) {
this.filenameToScriptInfo.remove(info.path);
this.lastDeletedFile = info;
// capture list of projects since detachAllProjects will wipe out original list
const containingProjects = info.containingProjects.slice();
@@ -351,6 +354,7 @@ namespace ts.server {
// update projects to make sure that set of referenced files is correct
this.updateProjectGraphs(containingProjects);
this.lastDeletedFile = undefined;
if (!this.eventHandler) {
return;
@@ -756,12 +760,14 @@ namespace ts.server {
}
private reportConfigFileDiagnostics(configFileName: string, diagnostics: Diagnostic[], triggerFile?: string) {
if (diagnostics && diagnostics.length > 0) {
this.eventHandler({
eventName: "configFileDiag",
data: { configFileName, diagnostics, triggerFile }
});
if (!this.eventHandler) {
return;
}
this.eventHandler({
eventName: "configFileDiag",
data: { configFileName, diagnostics: diagnostics || [], triggerFile }
});
}
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], clientFileName?: string) {
+7 -3
View File
@@ -52,7 +52,7 @@ namespace ts.server {
};
}
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R extends { resolvedFileName?: string }>(
names: string[],
containingFile: string,
cache: ts.FileMap<Map<string, T>>,
@@ -65,6 +65,7 @@ namespace ts.server {
const newResolutions = new StringMap<T>();
const resolvedModules: R[] = [];
const compilerOptions = this.getCompilationSettings();
const lastDeletedFileName = this.project.projectService.lastDeletedFile && this.project.projectService.lastDeletedFile.fileName;
for (const name of names) {
// check if this is a duplicate entry in the list
@@ -94,8 +95,11 @@ namespace ts.server {
return false;
}
if (getResult(resolution)) {
// TODO: consider checking failedLookupLocations
const result = getResult(resolution);
if (result) {
if (result.resolvedFileName && result.resolvedFileName === lastDeletedFileName) {
return false;
}
return true;
}
+36 -5
View File
@@ -20,16 +20,42 @@ namespace ts.server {
}
}
function isJsOrDtsFile(info: ScriptInfo) {
return info.scriptKind === ScriptKind.JS || info.scriptKind == ScriptKind.JSX || fileExtensionIs(info.fileName, ".d.ts");
function countEachFileTypes(infos: ScriptInfo[]): { js: number, jsx: number, ts: number, tsx: number, dts: number } {
const result = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 };
for (const info of infos) {
switch (info.scriptKind) {
case ScriptKind.JS:
result.js += 1;
break;
case ScriptKind.JSX:
result.jsx += 1;
break;
case ScriptKind.TS:
fileExtensionIs(info.fileName, ".d.ts")
? result.dts += 1
: result.ts += 1;
break;
case ScriptKind.TSX:
result.tsx += 1;
break;
}
}
return result;
}
function hasOneOrMoreJsAndNoTsFiles(project: Project) {
const counts = countEachFileTypes(project.getScriptInfos());
return counts.js > 0 && counts.ts === 0 && counts.tsx === 0;
}
export function allRootFilesAreJsOrDts(project: Project): boolean {
return project.getRootScriptInfos().every(isJsOrDtsFile);
const counts = countEachFileTypes(project.getRootScriptInfos());
return counts.ts === 0 && counts.tsx === 0;
}
export function allFilesAreJsOrDts(project: Project): boolean {
return project.getScriptInfos().every(isJsOrDtsFile);
const counts = countEachFileTypes(project.getScriptInfos());
return counts.ts === 0 && counts.tsx === 0;
}
export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles {
@@ -71,11 +97,16 @@ namespace ts.server {
public typesVersion = 0;
public isJsOnlyProject() {
public isNonTsProject() {
this.updateGraph();
return allFilesAreJsOrDts(this);
}
public isJsOnlyProject() {
this.updateGraph();
return hasOneOrMoreJsAndNoTsFiles(this);
}
constructor(
readonly projectKind: ProjectKind,
readonly projectService: ProjectService,
+343 -20
View File
@@ -1,4 +1,4 @@
/**
/**
* Declaration module describing the TypeScript Server protocol
*/
declare namespace ts.server.protocol {
@@ -98,19 +98,38 @@ declare namespace ts.server.protocol {
projectFileName?: string;
}
/**
* A request to get TODO comments from the file
*/
export interface TodoCommentRequest extends FileRequest {
arguments: TodoCommentRequestArgs;
}
/**
* Arguments for TodoCommentRequest request.
*/
export interface TodoCommentRequestArgs extends FileRequestArgs {
/**
* Array of target TodoCommentDescriptors that describes TODO comments to be found
*/
descriptors: TodoCommentDescriptor[];
}
/**
* A request to get indentation for a location in file
*/
export interface IndentationRequest extends FileLocationRequest {
arguments: IndentationRequestArgs;
}
/**
* Arguments for IndentationRequest request.
*/
export interface IndentationRequestArgs extends FileLocationRequestArgs {
/**
* An optional set of settings to be used when computing indentation.
* If argument is omitted - then it will use settings for file that were previously set via 'configure' request or global settings.
*/
options?: EditorSettings;
}
@@ -125,17 +144,26 @@ declare namespace ts.server.protocol {
}
/**
* A request to get the project information of the current file
* A request to get the project information of the current file.
*/
export interface ProjectInfoRequest extends Request {
arguments: ProjectInfoRequestArgs;
}
export interface ProjectRequest extends Request {
arguments: ProjectRequestArgs;
/**
* A request to retrieve compiler options diagnostics for a project
*/
export interface CompilerOptionsDiagnosticsRequest extends Request {
arguments: CompilerOptionsDiagnosticsRequestArgs;
}
export interface ProjectRequestArgs {
/**
* Arguments for CompilerOptionsDiagnosticsRequest request.
*/
export interface CompilerOptionsDiagnosticsRequestArgs {
/**
* Name of the project to retrieve compiler options diagnostics.
*/
projectFileName: string;
}
@@ -158,6 +186,11 @@ declare namespace ts.server.protocol {
languageServiceDisabled?: boolean;
}
/**
* Represents diagnostic info that includes location of diagnostic in two forms
* - start position and length of the error span
* - startLocation and endLocation - a pair of Location objects that store start/end line and offset of the error span.
*/
export interface DiagnosticWithLinePosition {
message: string;
start: number;
@@ -203,6 +236,53 @@ declare namespace ts.server.protocol {
position?: number;
}
/**
* Request for the available codefixes at a specific position.
*/
export interface CodeFixRequest extends Request {
arguments: CodeFixRequestArgs;
}
/**
* Instances of this interface specify errorcodes on a specific location in a sourcefile.
*/
export interface CodeFixRequestArgs extends FileRequestArgs {
/**
* The line number for the request (1-based).
*/
startLine?: number;
/**
* The character offset (on the line) for the request (1-based).
*/
startOffset?: number;
/**
* Position (can be specified instead of line/offset pair)
*/
startPosition?: number;
/**
* The line number for the request (1-based).
*/
endLine?: number;
/**
* The character offset (on the line) for the request (1-based).
*/
endOffset?: number;
/**
* Position (can be specified instead of line/offset pair)
*/
endPosition?: number;
/**
* Errorcodes we want to get the fixes for.
*/
errorCodes?: number[];
}
/**
* A request whose arguments specify a file location (file, line, col).
*/
@@ -210,13 +290,25 @@ declare namespace ts.server.protocol {
arguments: FileLocationRequestArgs;
}
export interface FileSpanRequestArgs extends FileRequestArgs {
start: number;
length: number;
/**
* A request to get semantic diagnostics for a span in the file
*/
export interface SemanticDiagnosticsRequest extends FileRequest {
arguments: SemanticDiagnosticsRequestArgs;
}
export interface FileSpanRequest extends FileRequest {
arguments: FileSpanRequestArgs;
/**
* Arguments for SemanticDiagnosticsRequest request.
*/
export interface SemanticDiagnosticsRequestArgs extends FileRequestArgs {
/**
* Start position of the span.
*/
start: number;
/**
* Length of the span.
*/
length: number;
}
/**
@@ -308,11 +400,20 @@ declare namespace ts.server.protocol {
body?: FileSpan[];
}
/**
* Request to get brace completion for a location in the file.
*/
export interface BraceCompletionRequest extends FileLocationRequest {
arguments: BraceCompletionRequestArgs;
}
/**
* Argument for BraceCompletionRequest request.
*/
export interface BraceCompletionRequestArgs extends FileLocationRequestArgs {
/**
* Kind of opening brace
*/
openingBrace: string;
}
@@ -344,10 +445,17 @@ declare namespace ts.server.protocol {
arguments: DocumentHighlightsRequestArgs;
}
/**
* Span augmented with extra information that denotes the kind of the highlighting to be used for span.
* Kind is taken from HighlightSpanKind type.
*/
export interface HighlightSpan extends TextSpan {
kind: string;
}
/**
* Represents a set of highligh spans for a give name
*/
export interface DocumentHighlightsItem {
/**
* File containing highlight spans.
@@ -360,6 +468,9 @@ declare namespace ts.server.protocol {
highlightSpans: HighlightSpan[];
}
/**
* Response for a DocumentHighlightsRequest request.
*/
export interface DocumentHighlightsResponse extends Response {
body?: DocumentHighlightsItem[];
}
@@ -423,12 +534,20 @@ declare namespace ts.server.protocol {
body?: ReferencesResponseBody;
}
/**
* Argument for RenameRequest request.
*/
export interface RenameRequestArgs extends FileLocationRequestArgs {
/**
* Should text at specified location be found/changed in comments?
*/
findInComments?: boolean;
/**
* Should text at specified location be found/changed in strings?
*/
findInStrings?: boolean;
}
/**
* Rename request; value of command field is "rename". Return
* response giving the file locations that reference the symbol
@@ -503,17 +622,53 @@ declare namespace ts.server.protocol {
body?: RenameResponseBody;
}
/**
* Represents a file in external project.
* External project is project whose set of files, compilation options and open\close state
* is maintained by the client (i.e. if all this data come from .csproj file in Visual Studio).
* External project will exist even if all files in it are closed and should be closed explicity.
* If external project includes one or more tsconfig.json/jsconfig.json files then tsserver will
* create configured project for every config file but will maintain a link that these projects were created
* as a result of opening external project so they should be removed once external project is closed.
*/
export interface ExternalFile {
/**
* Name of file file
*/
fileName: string;
/**
* Script kind of the file
*/
scriptKind?: ScriptKind;
/**
* Whether file has mixed content (i.e. .cshtml file that combines html markup with C#/JavaScript)
*/
hasMixedContent?: boolean;
/**
* Content of the file
*/
content?: string;
}
/**
* Represent an external project
*/
export interface ExternalProject {
/**
* Project name
*/
projectFileName: string;
/**
* List of root files in project
*/
rootFiles: ExternalFile[];
/**
* Compiler options for the project
*/
options: ExternalProjectCompilerOptions;
/**
* Explicitly specified typing options for the project
*/
typingOptions?: TypingOptions;
}
@@ -522,18 +677,45 @@ declare namespace ts.server.protocol {
* compiler settings.
*/
export interface ExternalProjectCompilerOptions extends CompilerOptions {
/**
* If compile on save is enabled for the project
*/
compileOnSave?: boolean;
}
/**
* Contains information about current project version
*/
export interface ProjectVersionInfo {
/**
* Project name
*/
projectName: string;
/**
* true if project is inferred or false if project is external or configured
*/
isInferred: boolean;
/**
* Project version
*/
version: number;
/**
* Current set of compiler options for project
*/
options: CompilerOptions;
}
/**
* Represents a set of changes that happen in project
*/
export interface ProjectChanges {
/**
* List of added files
*/
added: string[];
/**
* List of removed files
*/
removed: string[];
}
@@ -545,17 +727,41 @@ declare namespace ts.server.protocol {
* otherwise - assume that nothing is changed
*/
export interface ProjectFiles {
/**
* Information abount project verison
*/
info?: ProjectVersionInfo;
/**
* List of files in project (might be omitted if current state of project can be computed using only information from 'changes')
*/
files?: string[];
/**
* Set of changes in project (omitted if the entire set of files in project should be replaced)
*/
changes?: ProjectChanges;
}
/**
* Combines project information with project level errors.
*/
export interface ProjectFilesWithDiagnostics extends ProjectFiles {
/**
* List of errors in project
*/
projectErrors: DiagnosticWithLinePosition[];
}
/**
* Represents set of changes in open file
*/
export interface ChangedOpenFile {
/**
* Name of file
*/
fileName: string;
/**
* List of changes that should be applied to known open file
*/
changes: ts.TextChange[];
}
@@ -689,54 +895,117 @@ declare namespace ts.server.protocol {
arguments: OpenRequestArgs;
}
type OpenExternalProjectArgs = ExternalProject;
/**
* Request to open or update external project
*/
export interface OpenExternalProjectRequest extends Request {
arguments: OpenExternalProjectArgs;
}
export interface CloseExternalProjectRequestArgs {
projectFileName: string;
}
/**
* Arguments to OpenExternalProjectRequest request
*/
type OpenExternalProjectArgs = ExternalProject;
/**
* Request to open multiple external projects
*/
export interface OpenExternalProjectsRequest extends Request {
arguments: OpenExternalProjectsArgs;
}
/**
* Arguments to OpenExternalProjectsRequest
*/
export interface OpenExternalProjectsArgs {
/**
* List of external projects to open or update
*/
projects: ExternalProject[];
}
/**
* Request to close external project.
*/
export interface CloseExternalProjectRequest extends Request {
arguments: CloseExternalProjectRequestArgs;
}
/**
* Arguments to CloseExternalProjectRequest request
*/
export interface CloseExternalProjectRequestArgs {
/**
* Name of the project to close
*/
projectFileName: string;
}
/**
* Request to check if given list of projects is up-to-date and synchronize them if necessary
*/
export interface SynchronizeProjectListRequest extends Request {
arguments: SynchronizeProjectListRequestArgs;
}
/**
* Arguments to SynchronizeProjectListRequest
*/
export interface SynchronizeProjectListRequestArgs {
/**
* List of last known projects
*/
knownProjects: protocol.ProjectVersionInfo[];
}
/**
* Request to synchronize list of open files with the client
*/
export interface ApplyChangedToOpenFilesRequest extends Request {
arguments: ApplyChangedToOpenFilesRequestArgs;
}
/**
* Arguments to ApplyChangedToOpenFilesRequest
*/
export interface ApplyChangedToOpenFilesRequestArgs {
/**
* List of newly open files
*/
openFiles?: ExternalFile[];
/**
* List of open files files that were changes
*/
changedFiles?: ChangedOpenFile[];
/**
* List of files that were closed
*/
closedFiles?: string[];
}
export interface SetCompilerOptionsForInferredProjectsArgs {
options: ExternalProjectCompilerOptions;
}
/**
* Request to set compiler options for inferred projects.
* External projects are opened / closed explicitly.
* Configured projects are opened when user opens loose file that has 'tsconfig.json' or 'jsconfig.json' anywhere in one of containing folders.
* This configuration file will be used to obtain a list of files and configuration settings for the project.
* Inferred projects are created when user opens a loose file that is not the part of external project
* or configured project and will contain only open file and transitive closure of referenced files if 'useOneInferredProject' is false,
* or all open loose files and its transitive closure of referenced files if 'useOneInferredProject' is true.
*/
export interface SetCompilerOptionsForInferredProjectsRequest extends Request {
arguments: SetCompilerOptionsForInferredProjectsArgs;
}
/**
* Argument for SetCompilerOptionsForInferredProjectsRequest request.
*/
export interface SetCompilerOptionsForInferredProjectsArgs {
/**
* Compiler options to be used with inferred projects.
*/
options: ExternalProjectCompilerOptions;
}
/**
* Exit request; value of command field is "exit". Ask the server process
* to exit.
@@ -754,23 +1023,48 @@ declare namespace ts.server.protocol {
export interface CloseRequest extends FileRequest {
}
/**
* Request to obtain the list of files that should be regenerated if target file is recompiled.
* NOTE: this us query-only operation and does not generate any output on disk.
*/
export interface CompileOnSaveAffectedFileListRequest extends FileRequest {
}
/**
* Contains a list of files that should be regenerated in a project
*/
export interface CompileOnSaveAffectedFileListSingleProject {
/**
* Project name
*/
projectFileName: string;
/**
* List of files names that should be recompiled
*/
fileNames: string[];
}
/**
* Response for CompileOnSaveAffectedFileListRequest request;
*/
export interface CompileOnSaveAffectedFileListResponse extends Response {
body: CompileOnSaveAffectedFileListSingleProject[];
}
/**
* Request to recompile the file. All generated outputs (.js, .d.ts or .js.map files) is written on disk.
*/
export interface CompileOnSaveEmitFileRequest extends FileRequest {
args: CompileOnSaveEmitFileRequestArgs;
}
/**
* Arguments for CompileOnSaveEmitFileRequest
*/
export interface CompileOnSaveEmitFileRequestArgs extends FileRequestArgs {
/**
* if true - then file should be recompiled even if it does not have any changes.
*/
forced?: boolean;
}
@@ -839,7 +1133,14 @@ declare namespace ts.server.protocol {
*/
endOffset: number;
/**
* End position of the range for which to format text in file.
*/
endPosition?: number;
/**
* Format options to be used.
*/
options?: ts.FormatCodeOptions;
}
@@ -879,6 +1180,23 @@ declare namespace ts.server.protocol {
newText: string;
}
export interface FileCodeEdits {
fileName: string;
textChanges: CodeEdit[];
}
export interface CodeFixResponse extends Response {
/** The code actions that are available */
body?: CodeAction[];
}
export interface CodeAction {
/** Description of the code action to display in the UI of the editor */
description: string;
/** Text changes to apply to each file as part of the code action */
changes: FileCodeEdits[];
}
/**
* Format and format on key response message.
*/
@@ -1253,6 +1571,11 @@ declare namespace ts.server.protocol {
* Text of diagnostic message.
*/
text: string;
/**
* The error code of the diagnostic message.
*/
code?: number;
}
export interface DiagnosticEventBody {
+105 -28
View File
@@ -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" />
@@ -14,6 +14,17 @@ namespace ts.server {
return ((1e9 * seconds) + nanoseconds) / 1000000.0;
}
function shouldSkipSematicCheck(project: Project) {
if (project.getCompilerOptions().skipLibCheck !== undefined) {
return false;
}
if ((project.projectKind === ProjectKind.Inferred || project.projectKind === ProjectKind.External) && project.isJsOnlyProject()) {
return true;
}
return false;
}
interface FileStart {
file: string;
start: ILineInfo;
@@ -44,7 +55,8 @@ namespace ts.server {
return {
start: scriptInfo.positionToLineOffset(diag.start),
end: scriptInfo.positionToLineOffset(diag.start + diag.length),
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n")
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
code: diag.code
};
}
@@ -134,6 +146,9 @@ namespace ts.server {
export const NameOrDottedNameSpan = "nameOrDottedNameSpan";
export const BreakpointStatement = "breakpointStatement";
export const CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects";
export const GetCodeFixes = "getCodeFixes";
export const GetCodeFixesFull = "getCodeFixes-full";
export const GetSupportedCodeFixes = "getSupportedCodeFixes";
}
export function formatMessage<T extends protocol.Message>(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string {
@@ -155,6 +170,8 @@ namespace ts.server {
private immediateId: any;
private changeSeq = 0;
private eventHander: ProjectServiceEventHandler;
constructor(
private host: ServerHost,
cancellationToken: HostCancellationToken,
@@ -163,17 +180,18 @@ namespace ts.server {
private byteLength: (buf: string, encoding?: string) => number,
private hrtime: (start?: number[]) => number[],
protected logger: Logger,
protected readonly canUseEvents: boolean) {
protected readonly canUseEvents: boolean,
eventHandler?: ProjectServiceEventHandler) {
const eventHandler: ProjectServiceEventHandler = canUseEvents
? event => this.handleEvent(event)
this.eventHander = canUseEvents
? eventHandler || (event => this.defaultEventHandler(event))
: undefined;
this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler);
this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, this.eventHander);
this.gcTimer = new GcTimer(host, /*delay*/ 7000, logger);
}
private handleEvent(event: ProjectServiceEvent) {
private defaultEventHandler(event: ProjectServiceEvent) {
switch (event.eventName) {
case "context":
const { project, fileName } = event.data;
@@ -252,12 +270,13 @@ namespace ts.server {
private semanticCheck(file: NormalizedPath, project: Project) {
try {
const diags = project.getLanguageService().getSemanticDiagnostics(file);
if (diags) {
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag");
let diags: Diagnostic[] = [];
if (!shouldSkipSematicCheck(project)) {
diags = project.getLanguageService().getSemanticDiagnostics(file);
}
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag");
}
catch (err) {
this.logError(err, "semantic check");
@@ -342,7 +361,7 @@ namespace ts.server {
}
}
private getEncodedSemanticClassifications(args: protocol.FileSpanRequestArgs) {
private getEncodedSemanticClassifications(args: protocol.SemanticDiagnosticsRequestArgs) {
const { file, project } = this.getFileAndProject(args);
return project.getLanguageService().getEncodedSemanticClassifications(file, args);
}
@@ -351,7 +370,7 @@ namespace ts.server {
return projectFileName && this.projectService.findProject(projectFileName);
}
private getCompilerOptionsDiagnostics(args: protocol.ProjectRequestArgs) {
private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) {
const project = this.getProject(args.projectFileName);
return this.convertToDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics(), /*scriptInfo*/ undefined);
}
@@ -370,6 +389,9 @@ namespace ts.server {
private getDiagnosticsWorker(args: protocol.FileRequestArgs, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) {
const { project, file } = this.getFileAndProject(args);
if (shouldSkipSematicCheck(project)) {
return [];
}
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const diagnostics = selector(project, file);
return includeLinePosition
@@ -734,8 +756,11 @@ namespace ts.server {
*/
private openClientFile(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind) {
const { configFileName, configFileErrors } = this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind);
if (configFileErrors) {
this.configFileDiagnosticEvent(fileName, configFileName, configFileErrors);
if (this.eventHander) {
this.eventHander({
eventName: "configFileDiag",
data: { fileName, configFileName, diagnostics: configFileErrors || [] }
});
}
}
@@ -751,7 +776,7 @@ namespace ts.server {
return this.getFileAndProjectWorker(args.file, args.projectFileName, /*refreshInferredProjects*/ false, errorOnMissingProject);
}
private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, refreshInferredProjects: boolean, errorOnMissingProject: boolean) {
private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, refreshInferredProjects: boolean, errorOnMissingProject: boolean) {
const file = toNormalizedPath(uncheckedFileName);
const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file, refreshInferredProjects);
if (!project && errorOnMissingProject) {
@@ -842,13 +867,7 @@ namespace ts.server {
return undefined;
}
return edits.map((edit) => {
return {
start: scriptInfo.positionToLineOffset(edit.span.start),
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(edit.span)),
newText: edit.newText ? edit.newText : ""
};
});
return edits.map(edit => this.convertTextChangeToCodeEdit(edit, scriptInfo));
}
private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) {
@@ -1127,7 +1146,7 @@ namespace ts.server {
return combineProjectOutput(
projects,
project => {
const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isJsOnlyProject());
const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject());
if (!navItems) {
return [];
}
@@ -1165,7 +1184,7 @@ namespace ts.server {
else {
return combineProjectOutput(
projects,
project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isJsOnlyProject()),
project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()),
/*comparer*/ undefined,
navigateToItemIsEqualTo);
}
@@ -1199,6 +1218,55 @@ namespace ts.server {
}
}
private getSupportedCodeFixes(): string[] {
return ts.getSupportedCodeFixes();
}
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const startPosition = getStartPosition();
const endPosition = getEndPosition();
const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes);
if (!codeActions) {
return undefined;
}
if (simplifiedResult) {
return codeActions.map(codeAction => this.mapCodeAction(codeAction, scriptInfo));
}
else {
return codeActions;
}
function getStartPosition() {
return args.startPosition !== undefined ? args.startPosition : scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset);
}
function getEndPosition() {
return args.endPosition !== undefined ? args.endPosition : scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
}
}
private mapCodeAction(codeAction: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
return {
description: codeAction.description,
changes: codeAction.changes.map(change => ({
fileName: change.fileName,
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
}))
};
}
private convertTextChangeToCodeEdit(change: ts.TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
return {
start: scriptInfo.positionToLineOffset(change.span.start),
end: scriptInfo.positionToLineOffset(change.span.start + change.span.length),
newText: change.newText ? change.newText : ""
};
}
private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
@@ -1438,10 +1506,10 @@ namespace ts.server {
[CommandNames.SignatureHelpFull]: (request: protocol.SignatureHelpRequest) => {
return this.requiredResponse(this.getSignatureHelpItems(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.CompilerOptionsDiagnosticsFull]: (request: protocol.ProjectRequest) => {
[CommandNames.CompilerOptionsDiagnosticsFull]: (request: protocol.CompilerOptionsDiagnosticsRequest) => {
return this.requiredResponse(this.getCompilerOptionsDiagnostics(request.arguments));
},
[CommandNames.EncodedSemanticClassificationsFull]: (request: protocol.FileSpanRequest) => {
[CommandNames.EncodedSemanticClassificationsFull]: (request: protocol.SemanticDiagnosticsRequest) => {
return this.requiredResponse(this.getEncodedSemanticClassifications(request.arguments));
},
[CommandNames.Cleanup]: (request: protocol.Request) => {
@@ -1521,6 +1589,15 @@ namespace ts.server {
[CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => {
this.projectService.reloadProjects();
return this.notRequired();
},
[CommandNames.GetCodeFixes]: (request: protocol.CodeFixRequest) => {
return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => {
return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.GetSupportedCodeFixes]: (request: protocol.Request) => {
return this.requiredResponse(this.getSupportedCodeFixes());
}
});
+2 -1
View File
@@ -175,7 +175,8 @@ namespace ts.server {
dispose: (): any => throwLanguageServiceIsDisabledError(),
getCompletionEntrySymbol: (): any => throwLanguageServiceIsDisabledError(),
getImplementationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
getSourceFile: (): any => throwLanguageServiceIsDisabledError()
getSourceFile: (): any => throwLanguageServiceIsDisabledError(),
getCodeFixesAtPosition: (): any => throwLanguageServiceIsDisabledError()
};
export interface ServerLanguageServiceHost {
+47
View File
@@ -0,0 +1,47 @@
/* @internal */
namespace ts {
export interface CodeFix {
errorCodes: number[];
getCodeActions(context: CodeFixContext): CodeAction[] | undefined;
}
export interface CodeFixContext {
errorCode: number;
sourceFile: SourceFile;
span: TextSpan;
program: Program;
newLineCharacter: string;
}
export namespace codefix {
const codeFixes = new NumberMap<number, CodeFix[]>();
export function registerCodeFix(action: CodeFix) {
forEach(action.errorCodes, error => {
multiMapAdd(codeFixes, error, action);
});
}
export function getSupportedErrorCodes() {
const supportedErrorCodes: string[] = [];
codeFixes.forEach((_, key) => {
supportedErrorCodes.push(key.toString())
})
return supportedErrorCodes;
}
export function getFixes(context: CodeFixContext): CodeAction[] {
const fixes = codeFixes.get(context.errorCode);
let allActions: CodeAction[] = [];
forEach(fixes, f => {
const actions = f.getCodeActions(context);
if (actions && actions.length > 0) {
allActions = allActions.concat(actions);
}
});
return allActions;
}
}
}
+1
View File
@@ -0,0 +1 @@
///<reference path='superFixes.ts' />
+81
View File
@@ -0,0 +1,81 @@
/* @internal */
namespace ts.codefix {
function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
// First token is the open curly, this is where we want to put the 'super' call.
return constructor.body.getFirstToken(sourceFile).getEnd();
}
registerCodeFix({
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
if (token.kind !== SyntaxKind.ConstructorKeyword) {
return undefined;
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>token.parent, sourceFile);
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }]
}];
}
});
registerCodeFix({
errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
if (token.kind !== SyntaxKind.ThisKeyword) {
return undefined;
}
const constructor = getContainingFunction(token);
const superCall = findSuperCall((<ConstructorDeclaration>constructor).body);
if (!superCall) {
return undefined;
}
// figure out if the this access is actuall inside the supercall
// i.e. super(this.a), since in that case we won't suggest a fix
if (superCall.expression && superCall.expression.kind == SyntaxKind.CallExpression) {
const arguments = (<CallExpression>superCall.expression).arguments;
for (let i = 0; i < arguments.length; i++) {
if ((<PropertyAccessExpression>arguments[i]).expression === token) {
return undefined;
}
}
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>constructor, sourceFile);
const changes = [{
fileName: sourceFile.fileName, textChanges: [{
newText: superCall.getText(sourceFile),
span: { start: newPosition, length: 0 }
},
{
newText: "",
span: { start: superCall.getStart(sourceFile), length: superCall.getWidth(sourceFile) }
}]
}];
return [{
description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor),
changes
}];
function findSuperCall(n: Node): ExpressionStatement {
if (n.kind === SyntaxKind.ExpressionStatement && isSuperCall((<ExpressionStatement>n).expression)) {
return <ExpressionStatement>n;
}
if (isFunctionLike(n)) {
return undefined;
}
return forEachChild(n, findSuperCall);
}
}
});
}
+1 -1
View File
@@ -324,7 +324,7 @@ namespace ts.NavigationBar {
}
// More efficient to create a collator once and use its `compare` than to call `a.localeCompare(b)` many times.
const collator: { compare(a: string, b: string): number } = typeof Intl === "undefined" ? undefined : new Intl.Collator();
const collator: { compare(a: string, b: string): number } = typeof Intl === "object" && typeof Intl.Collator === "function" ? new Intl.Collator() : undefined;
// Intl is missing in Safari, and node 0.10 treats "a" as greater than "B".
const localeCompareIsCorrect = collator && collator.compare("a", "B") < 0;
const localeCompareFix: (a: string, b: string) => number = localeCompareIsCorrect ? collator.compare : function(a, b) {
+38 -2
View File
@@ -24,6 +24,8 @@
/// <reference path='transpile.ts' />
/// <reference path='formatting\formatting.ts' />
/// <reference path='formatting\smartIndenter.ts' />
/// <reference path='codefixes\codeFixProvider.ts' />
/// <reference path='codefixes\fixes.ts' />
namespace ts {
/** The version of the language service API */
@@ -664,6 +666,7 @@ namespace ts {
return {
getNodeConstructor: () => NodeObject,
getTokenConstructor: () => TokenObject,
getIdentifierConstructor: () => IdentifierObject,
getSourceFileConstructor: () => SourceFileObject,
getSymbolConstructor: () => SymbolObject,
@@ -730,9 +733,13 @@ namespace ts {
};
}
// Cache host information about script should be refreshed
export function getSupportedCodeFixes() {
return codefix.getSupportedErrorCodes();
}
// Cache host information about script Should be refreshed
// at each language service public entry point, since we don't know when
// set of scripts handled by the host changes.
// the set of scripts handled by the host changes.
class HostCache {
private fileNameToEntry: FileMap<HostFileInformation>;
private _compilationSettings: CompilerOptions;
@@ -1654,6 +1661,34 @@ namespace ts {
return [];
}
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const span = { start, length: end - start };
const newLineChar = getNewLineOrDefaultFromHost(host);
let allFixes: CodeAction[] = [];
forEach(errorCodes, error => {
cancellationToken.throwIfCancellationRequested();
const context = {
errorCode: error,
sourceFile: sourceFile,
span: span,
program: program,
newLineCharacter: newLineChar
};
const fixes = codefix.getFixes(context);
if (fixes) {
allFixes = allFixes.concat(fixes);
}
});
return allFixes;
}
function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position);
}
@@ -1877,6 +1912,7 @@ namespace ts {
getFormattingEditsAfterKeystroke,
getDocCommentTemplateAtPosition,
isValidBraceCompletionAtPosition,
getCodeFixesAtPosition,
getEmitOutput,
getNonBoundSourceFile,
getSourceFile,
+3 -1
View File
@@ -78,6 +78,8 @@
"formatting/rulesMap.ts",
"formatting/rulesProvider.ts",
"formatting/smartIndenter.ts",
"formatting/tokenRange.ts"
"formatting/tokenRange.ts",
"codeFixes/codeFixProvider.ts",
"codeFixes/fixes.ts"
]
}
+14
View File
@@ -239,6 +239,8 @@ namespace ts {
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[];
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getProgram(): Program;
@@ -291,6 +293,18 @@ namespace ts {
newText: string;
}
export interface FileTextChanges {
fileName: string;
textChanges: TextChange[];
}
export interface CodeAction {
/** Description of the code action to display in the UI of the editor */
description: string;
/** Text changes to apply to each file as part of the code action */
changes: FileTextChanges[];
}
export interface TextInsertion {
newText: string;
/** The position in newText the caret should point to after the insertion. */
+2
View File
@@ -136,6 +136,7 @@ declare namespace FourSlashInterface {
typeDefinitionCountIs(expectedCount: number): void;
implementationListIsEmpty(): void;
isValidBraceCompletionAtPosition(openingBrace?: string): void;
codeFixAvailable(): void;
}
class verify extends verifyNegatable {
assertHasRanges(ranges: Range[]): void;
@@ -208,6 +209,7 @@ declare namespace FourSlashInterface {
noMatchingBracePositionInCurrentFile(bracePosition: number): void;
DocCommentTemplate(expectedText: string, expectedOffset: number, empty?: boolean): void;
noDocCommentTemplate(): void;
codeFixAtPosition(expectedText: string, errorCode?: number): void;
navigationBar(json: any): void;
navigationItemsListCount(count: number, searchValue: string, matchKind?: string, fileName?: string): void;
@@ -0,0 +1,7 @@
///<reference path="fourslash.ts" />
/////** DOC */
////type /**/T = number
goTo.marker();
verify.quickInfoIs("type T = number", "DOC ");
+10
View File
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />
////class Base{
////}
////class C extends Base{
//// constructor() {[| |]
//// }
////}
verify.codeFixAtPosition('super();');
+10
View File
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />
////class Base{
////}
////class C extends Base{
//// constructor() {[| |]
//// }
////}
verify.codeFixAtPosition('super();');
+13
View File
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
////class Base{
////}
////class C extends Base{
//// private a:number;
//// constructor() {[|
//// this.a = 12;
//// super();|]
//// }
////}
verify.codeFixAtPosition("super(); this.a = 12;");
+12
View File
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts' />
////class Base{
//// constructor(id: number) { }
////}
////class C extends Base{
//// constructor(private a:number) {
//// super(this.a);
//// }
////}
verify.not.codeFixAvailable();