mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into strictObjectLiterals
This commit is contained in:
+29
-23
@@ -783,13 +783,13 @@ namespace FourSlash {
|
||||
});
|
||||
}
|
||||
|
||||
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
|
||||
public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
|
||||
const completions = this.getCompletionListAtCaret();
|
||||
if (completions) {
|
||||
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex, hasAction);
|
||||
this.assertItemInCompletionList(completions.entries, entryId, text, documentation, kind, spanIndex, hasAction);
|
||||
}
|
||||
else {
|
||||
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
|
||||
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${JSON.stringify(entryId)}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,7 +804,7 @@ namespace FourSlash {
|
||||
* @param expectedKind the kind of symbol (see ScriptElementKind)
|
||||
* @param spanIndex the index of the range that the completion item's replacement text span should match
|
||||
*/
|
||||
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
|
||||
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
|
||||
const that = this;
|
||||
let replacementSpan: ts.TextSpan;
|
||||
if (spanIndex !== undefined) {
|
||||
@@ -833,14 +833,14 @@ namespace FourSlash {
|
||||
|
||||
const completions = this.getCompletionListAtCaret();
|
||||
if (completions) {
|
||||
let filterCompletions = completions.entries.filter(e => e.name === symbol);
|
||||
let filterCompletions = completions.entries.filter(e => e.name === entryId.name && e.source === entryId.source);
|
||||
filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind) : filterCompletions;
|
||||
filterCompletions = filterCompletions.filter(filterByTextOrDocumentation);
|
||||
if (filterCompletions.length !== 0) {
|
||||
// After filtered using all present criterion, if there are still symbol left in the list
|
||||
// then these symbols must meet the criterion for Not supposed to be in the list. So we
|
||||
// raise an error
|
||||
let error = "Completion list did contain \'" + symbol + "\'.";
|
||||
let error = `Completion list did contain '${JSON.stringify(entryId)}\'.`;
|
||||
const details = this.getCompletionEntryDetails(filterCompletions[0].name);
|
||||
if (expectedText) {
|
||||
error += "Expected text: " + expectedText + " to equal: " + ts.displayPartsToString(details.displayParts) + ".";
|
||||
@@ -1130,8 +1130,8 @@ Actual: ${stringify(fullActual)}`);
|
||||
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition);
|
||||
}
|
||||
|
||||
private getCompletionEntryDetails(entryName: string) {
|
||||
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings);
|
||||
private getCompletionEntryDetails(entryName: string, source?: string) {
|
||||
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source);
|
||||
}
|
||||
|
||||
private getReferencesAtCaret() {
|
||||
@@ -1640,7 +1640,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
const longestNameLength = max(entries, m => m.name.length);
|
||||
const longestKindLength = max(entries, m => m.kind.length);
|
||||
entries.sort((m, n) => m.sortText > n.sortText ? 1 : m.sortText < n.sortText ? -1 : m.name > n.name ? 1 : m.name < n.name ? -1 : 0);
|
||||
const membersString = entries.map(m => `${pad(m.name, longestNameLength)} ${pad(m.kind, longestKindLength)} ${m.kindModifiers}`).join("\n");
|
||||
const membersString = entries.map(m => `${pad(m.name, longestNameLength)} ${pad(m.kind, longestKindLength)} ${m.kindModifiers} ${m.source === undefined ? "" : m.source}`).join("\n");
|
||||
Harness.IO.log(membersString);
|
||||
}
|
||||
|
||||
@@ -2296,13 +2296,13 @@ Actual: ${stringify(fullActual)}`);
|
||||
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
|
||||
this.goToMarker(markerName);
|
||||
|
||||
const actualCompletion = this.getCompletionListAtCaret().entries.find(e => e.name === options.name);
|
||||
const actualCompletion = this.getCompletionListAtCaret().entries.find(e => e.name === options.name && e.source === options.source);
|
||||
|
||||
if (!actualCompletion.hasAction) {
|
||||
this.raiseError(`Completion for ${options.name} does not have an associated action.`);
|
||||
}
|
||||
|
||||
const details = this.getCompletionEntryDetails(options.name);
|
||||
const details = this.getCompletionEntryDetails(options.name, actualCompletion.source);
|
||||
if (details.codeActions.length !== 1) {
|
||||
this.raiseError(`Expected one code action, got ${details.codeActions.length}`);
|
||||
}
|
||||
@@ -2984,7 +2984,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
|
||||
private assertItemInCompletionList(
|
||||
items: ts.CompletionEntry[],
|
||||
name: string,
|
||||
entryId: ts.Completions.CompletionEntryIdentifier,
|
||||
text: string | undefined,
|
||||
documentation: string | undefined,
|
||||
kind: string | undefined,
|
||||
@@ -2992,25 +2992,27 @@ Actual: ${stringify(fullActual)}`);
|
||||
hasAction: boolean | undefined,
|
||||
) {
|
||||
for (const item of items) {
|
||||
if (item.name === name) {
|
||||
if (documentation !== undefined || text !== undefined) {
|
||||
const details = this.getCompletionEntryDetails(item.name);
|
||||
if (item.name === entryId.name && item.source === entryId.source) {
|
||||
if (documentation !== undefined || text !== undefined || entryId.source !== undefined) {
|
||||
const details = this.getCompletionEntryDetails(item.name, item.source);
|
||||
|
||||
if (documentation !== undefined) {
|
||||
assert.equal(ts.displayPartsToString(details.documentation), documentation, this.assertionMessageAtLastKnownMarker("completion item documentation for " + name));
|
||||
assert.equal(ts.displayPartsToString(details.documentation), documentation, this.assertionMessageAtLastKnownMarker("completion item documentation for " + entryId));
|
||||
}
|
||||
if (text !== undefined) {
|
||||
assert.equal(ts.displayPartsToString(details.displayParts), text, this.assertionMessageAtLastKnownMarker("completion item detail text for " + name));
|
||||
assert.equal(ts.displayPartsToString(details.displayParts), text, this.assertionMessageAtLastKnownMarker("completion item detail text for " + entryId));
|
||||
}
|
||||
|
||||
assert.deepEqual(details.source, entryId.source === undefined ? undefined : [ts.textPart(entryId.source)]);
|
||||
}
|
||||
|
||||
if (kind !== undefined) {
|
||||
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name));
|
||||
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId));
|
||||
}
|
||||
|
||||
if (spanIndex !== undefined) {
|
||||
const span = this.getTextSpanForRangeAtIndex(spanIndex);
|
||||
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name));
|
||||
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + entryId));
|
||||
}
|
||||
|
||||
assert.equal(item.hasAction, hasAction);
|
||||
@@ -3021,7 +3023,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
|
||||
const itemsString = items.map(item => stringify({ name: item.name, kind: item.kind })).join(",\n");
|
||||
|
||||
this.raiseError(`Expected "${stringify({ name, text, documentation, kind })}" to be in list [${itemsString}]`);
|
||||
this.raiseError(`Expected "${stringify({ entryId, text, documentation, kind })}" to be in list [${itemsString}]`);
|
||||
}
|
||||
|
||||
private findFile(indexOrName: any) {
|
||||
@@ -3732,12 +3734,15 @@ namespace FourSlashInterface {
|
||||
|
||||
// Verifies the completion list contains the specified symbol. The
|
||||
// completion list is brought up if necessary
|
||||
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
|
||||
public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
|
||||
if (typeof entryId === "string") {
|
||||
entryId = { name: entryId, source: undefined };
|
||||
}
|
||||
if (this.negative) {
|
||||
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex);
|
||||
this.state.verifyCompletionListDoesNotContain(entryId, text, documentation, kind, spanIndex);
|
||||
}
|
||||
else {
|
||||
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex, hasAction);
|
||||
this.state.verifyCompletionListContains(entryId, text, documentation, kind, spanIndex, hasAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4492,6 +4497,7 @@ namespace FourSlashInterface {
|
||||
|
||||
export interface VerifyCompletionActionOptions extends NewContentOptions {
|
||||
name: string;
|
||||
source?: string;
|
||||
description: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1196,6 +1196,9 @@ namespace Harness {
|
||||
traceResults = [];
|
||||
compilerHost.trace = text => traceResults.push(text);
|
||||
}
|
||||
else {
|
||||
compilerHost.directoryExists = () => true; // This only visibly affects resolution traces, so to save time we always return true where possible
|
||||
}
|
||||
const program = ts.createProgram(programFileNames, options, compilerHost);
|
||||
|
||||
const emitResult = program.emit();
|
||||
|
||||
+12
-8
@@ -89,6 +89,7 @@ interface PlaybackControl {
|
||||
namespace Playback {
|
||||
let recordLog: IOLog = undefined;
|
||||
let replayLog: IOLog = undefined;
|
||||
let replayFilesRead: ts.Map<IOLogFile> | undefined = undefined;
|
||||
let recordLogFileNameBase = "";
|
||||
|
||||
interface Memoized<T> {
|
||||
@@ -222,10 +223,15 @@ namespace Playback {
|
||||
replayLog = log;
|
||||
// Remove non-found files from the log (shouldn't really need them, but we still record them for diagnostic purposes)
|
||||
replayLog.filesRead = replayLog.filesRead.filter(f => f.result.contents !== undefined);
|
||||
replayFilesRead = ts.createMap();
|
||||
for (const file of replayLog.filesRead) {
|
||||
replayFilesRead.set(ts.normalizeSlashes(file.path).toLowerCase(), file);
|
||||
}
|
||||
};
|
||||
|
||||
wrapper.endReplay = () => {
|
||||
replayLog = undefined;
|
||||
replayFilesRead = undefined;
|
||||
};
|
||||
|
||||
wrapper.startRecord = (fileNameBase) => {
|
||||
@@ -254,7 +260,7 @@ namespace Playback {
|
||||
path => callAndRecord(underlying.fileExists(path), recordLog.fileExists, { path }),
|
||||
memoize(path => {
|
||||
// If we read from the file, it must exist
|
||||
if (findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ false)) {
|
||||
if (findFileByPath(path, /*throwFileNotFoundError*/ false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@@ -298,7 +304,7 @@ namespace Playback {
|
||||
recordLog.filesRead.push(logEntry);
|
||||
return result;
|
||||
},
|
||||
memoize(path => findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ true).contents));
|
||||
memoize(path => findFileByPath(path, /*throwFileNotFoundError*/ true).contents));
|
||||
|
||||
wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)(
|
||||
(path, extensions, exclude, include, depth) => {
|
||||
@@ -379,14 +385,12 @@ namespace Playback {
|
||||
return results[0].result;
|
||||
}
|
||||
|
||||
function findFileByPath(logArray: IOLogFile[],
|
||||
expectedPath: string, throwFileNotFoundError: boolean): FileInformation {
|
||||
function findFileByPath(expectedPath: string, throwFileNotFoundError: boolean): FileInformation {
|
||||
const normalizedName = ts.normalizePath(expectedPath).toLowerCase();
|
||||
// Try to find the result through normal fileName
|
||||
for (const log of logArray) {
|
||||
if (ts.normalizeSlashes(log.path).toLowerCase() === normalizedName) {
|
||||
return log.result;
|
||||
}
|
||||
const result = replayFilesRead.get(normalizedName);
|
||||
if (result) {
|
||||
return result.result;
|
||||
}
|
||||
|
||||
// If we got here, we didn't find a match
|
||||
|
||||
@@ -39,8 +39,6 @@ namespace RWC {
|
||||
const baseName = ts.getBaseFileName(jsonPath);
|
||||
let currentDirectory: string;
|
||||
let useCustomLibraryFile: boolean;
|
||||
let skipTypeBaselines = false;
|
||||
const typeSizeLimit = 10000000;
|
||||
after(() => {
|
||||
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
||||
// Therefore we have to clean out large objects after the test is done.
|
||||
@@ -54,7 +52,6 @@ namespace RWC {
|
||||
// or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file
|
||||
// otherwise use the lib.d.ts from built/local
|
||||
useCustomLibraryFile = undefined;
|
||||
skipTypeBaselines = false;
|
||||
});
|
||||
|
||||
it("can compile", function(this: Mocha.ITestCallbackContext) {
|
||||
@@ -64,7 +61,6 @@ namespace RWC {
|
||||
const ioLog: IOLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)), Harness.IO, `internal/cases/rwc/${baseName}`);
|
||||
currentDirectory = ioLog.currentDirectory;
|
||||
useCustomLibraryFile = ioLog.useCustomLibraryFile;
|
||||
skipTypeBaselines = ioLog.filesRead.reduce((acc, elem) => (elem && elem.result && elem.result.contents) ? acc + elem.result.contents.length : acc, 0) > typeSizeLimit;
|
||||
runWithIOLog(ioLog, () => {
|
||||
opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName));
|
||||
assert.equal(opts.errors.length, 0);
|
||||
@@ -199,14 +195,6 @@ namespace RWC {
|
||||
}, baselineOpts, [".map"]);
|
||||
});
|
||||
|
||||
/*it("has correct source map record", () => {
|
||||
if (compilerOptions.sourceMap) {
|
||||
Harness.Baseline.runBaseline(baseName + ".sourcemap.txt", () => {
|
||||
return compilerResult.getSourceMapRecord();
|
||||
}, baselineOpts);
|
||||
}
|
||||
});*/
|
||||
|
||||
it("has the expected errors", () => {
|
||||
Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => {
|
||||
if (compilerResult.errors.length === 0) {
|
||||
@@ -219,14 +207,6 @@ namespace RWC {
|
||||
}, baselineOpts);
|
||||
});
|
||||
|
||||
it("has the expected types", () => {
|
||||
// We don't need to pass the extension here because "doTypeAndSymbolBaseline" will append appropriate extension of ".types" or ".symbols"
|
||||
Harness.Compiler.doTypeAndSymbolBaseline(baseName, compilerResult.program, inputFiles
|
||||
.concat(otherFiles)
|
||||
.filter(file => !!compilerResult.program.getSourceFile(file.unitName))
|
||||
.filter(e => !Harness.isDefaultLibraryFile(e.unitName)), baselineOpts, /*multifile*/ true, skipTypeBaselines, /*skipSymbolbaselines*/ true);
|
||||
});
|
||||
|
||||
// Ideally, a generated declaration file will have no errors. But we allow generated
|
||||
// declaration file errors as part of the baseline.
|
||||
it("has the expected errors in generated declaration files", () => {
|
||||
|
||||
@@ -362,27 +362,32 @@ function parsePrimaryExpression(): any {
|
||||
}`);
|
||||
|
||||
testExtractFunction("extractFunction_VariableDeclaration_Var", `
|
||||
[#|var x = 1;|]
|
||||
[#|var x = 1;
|
||||
"hello"|]
|
||||
x;
|
||||
`);
|
||||
|
||||
testExtractFunction("extractFunction_VariableDeclaration_Let_Type", `
|
||||
[#|let x: number = 1;|]
|
||||
[#|let x: number = 1;
|
||||
"hello";|]
|
||||
x;
|
||||
`);
|
||||
|
||||
testExtractFunction("extractFunction_VariableDeclaration_Let_NoType", `
|
||||
[#|let x = 1;|]
|
||||
[#|let x = 1;
|
||||
"hello";|]
|
||||
x;
|
||||
`);
|
||||
|
||||
testExtractFunction("extractFunction_VariableDeclaration_Const_Type", `
|
||||
[#|const x: number = 1;|]
|
||||
[#|const x: number = 1;
|
||||
"hello";|]
|
||||
x;
|
||||
`);
|
||||
|
||||
testExtractFunction("extractFunction_VariableDeclaration_Const_NoType", `
|
||||
[#|const x = 1;|]
|
||||
[#|const x = 1;
|
||||
"hello";|]
|
||||
x;
|
||||
`);
|
||||
|
||||
@@ -404,7 +409,8 @@ x; y; z;
|
||||
`);
|
||||
|
||||
testExtractFunction("extractFunction_VariableDeclaration_ConsumedTwice", `
|
||||
[#|const x: number = 1;|]
|
||||
[#|const x: number = 1;
|
||||
"hello";|]
|
||||
x; x;
|
||||
`);
|
||||
|
||||
|
||||
@@ -162,6 +162,19 @@ namespace ts {
|
||||
}|]|]
|
||||
}
|
||||
`);
|
||||
|
||||
// Variable statements
|
||||
testExtractRange(`[#|let x = [$|1|];|]`);
|
||||
testExtractRange(`[#|let x = [$|1|], y;|]`);
|
||||
testExtractRange(`[#|[$|let x = 1, y = 1;|]|]`);
|
||||
|
||||
// Variable declarations
|
||||
testExtractRange(`let [#|x = [$|1|]|];`);
|
||||
testExtractRange(`let [#|x = [$|1|]|], y = 2;`);
|
||||
testExtractRange(`let x = 1, [#|y = [$|2|]|];`);
|
||||
|
||||
// Return statements
|
||||
testExtractRange(`[#|return [$|1|];|]`);
|
||||
});
|
||||
|
||||
testExtractRangeFailed("extractRangeFailed1",
|
||||
@@ -340,6 +353,18 @@ switch (x) {
|
||||
refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalBreakOrContinueStatements.message
|
||||
]);
|
||||
|
||||
testExtractRangeFailed("extractRangeFailed12",
|
||||
`let [#|x|];`,
|
||||
[
|
||||
refactor.extractSymbol.Messages.StatementOrExpressionExpected.message
|
||||
]);
|
||||
|
||||
testExtractRangeFailed("extractRangeFailed13",
|
||||
`[#|return;|]`,
|
||||
[
|
||||
refactor.extractSymbol.Messages.CannotExtractRange.message
|
||||
]);
|
||||
|
||||
testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.CannotExtractIdentifier.message]);
|
||||
});
|
||||
}
|
||||
@@ -67,7 +67,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
export const newLineCharacter = "\n";
|
||||
export function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
|
||||
export const getRuleProvider = memoize(getRuleProviderInternal);
|
||||
function getRuleProviderInternal() {
|
||||
const options = {
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
@@ -89,9 +90,6 @@ namespace ts {
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
if (action) {
|
||||
action(options);
|
||||
}
|
||||
const rulesProvider = new formatting.RulesProvider();
|
||||
rulesProvider.ensureUpToDate(options);
|
||||
return rulesProvider;
|
||||
|
||||
@@ -356,7 +356,7 @@ namespace ts.projectSystem {
|
||||
}
|
||||
|
||||
function checkOpenFiles(projectService: server.ProjectService, expectedFiles: FileOrFolder[]) {
|
||||
checkFileNames("Open files", projectService.openFiles.map(info => info.fileName), expectedFiles.map(file => file.path));
|
||||
checkFileNames("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path).fileName), expectedFiles.map(file => file.path));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3893,6 +3893,96 @@ namespace ts.projectSystem {
|
||||
assert.equal(snap2.getText(0, snap2.getLength()), f1.content, "content should be equal to the content of original file");
|
||||
|
||||
});
|
||||
|
||||
it("should work when script info doesnt have any project open", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let x = 1"
|
||||
};
|
||||
const tmp = {
|
||||
path: "/a/b/app.tmp",
|
||||
content: "const y = 42"
|
||||
};
|
||||
const host = createServerHost([f1, tmp, libFile]);
|
||||
const session = createSession(host);
|
||||
const openContent = "let z = 1";
|
||||
// send open request
|
||||
session.executeCommandSeq(<server.protocol.OpenRequest>{
|
||||
command: server.protocol.CommandTypes.Open,
|
||||
arguments: { file: f1.path, fileContent: openContent }
|
||||
});
|
||||
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
const info = projectService.getScriptInfo(f1.path);
|
||||
assert.isDefined(info);
|
||||
checkScriptInfoContents(openContent, "contents set during open request");
|
||||
|
||||
// send close request
|
||||
session.executeCommandSeq(<server.protocol.CloseRequest>{
|
||||
command: server.protocol.CommandTypes.Close,
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(0, f1.content, "contents of closed file");
|
||||
|
||||
// Can reload contents of the file when its not open and has no project
|
||||
// reload from temp file
|
||||
session.executeCommandSeq(<server.protocol.ReloadRequest>{
|
||||
command: server.protocol.CommandTypes.Reload,
|
||||
arguments: { file: f1.path, tmpfile: tmp.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(0, tmp.content, "contents of temp file");
|
||||
|
||||
// reload from own file
|
||||
session.executeCommandSeq(<server.protocol.ReloadRequest>{
|
||||
command: server.protocol.CommandTypes.Reload,
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(0, f1.content, "contents of closed file");
|
||||
|
||||
// Open file again without setting its content
|
||||
session.executeCommandSeq(<server.protocol.OpenRequest>{
|
||||
command: server.protocol.CommandTypes.Open,
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(1, f1.content, "contents of file when opened without specifying contents");
|
||||
const snap = info.getSnapshot();
|
||||
|
||||
// send close request
|
||||
session.executeCommandSeq(<server.protocol.CloseRequest>{
|
||||
command: server.protocol.CommandTypes.Close,
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(0, f1.content, "contents of closed file");
|
||||
assert.strictEqual(info.getSnapshot(), snap);
|
||||
|
||||
// reload from temp file
|
||||
session.executeCommandSeq(<server.protocol.ReloadRequest>{
|
||||
command: server.protocol.CommandTypes.Reload,
|
||||
arguments: { file: f1.path, tmpfile: tmp.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(0, tmp.content, "contents of temp file");
|
||||
assert.notStrictEqual(info.getSnapshot(), snap);
|
||||
|
||||
// reload from own file
|
||||
session.executeCommandSeq(<server.protocol.ReloadRequest>{
|
||||
command: server.protocol.CommandTypes.Reload,
|
||||
arguments: { file: f1.path }
|
||||
});
|
||||
checkScriptInfoAndProjects(0, f1.content, "contents of closed file");
|
||||
assert.notStrictEqual(info.getSnapshot(), snap);
|
||||
|
||||
function checkScriptInfoAndProjects(inferredProjects: number, contentsOfInfo: string, captionForContents: string) {
|
||||
checkNumberOfProjects(projectService, { inferredProjects });
|
||||
assert.strictEqual(projectService.getScriptInfo(f1.path), info);
|
||||
checkScriptInfoContents(contentsOfInfo, captionForContents);
|
||||
}
|
||||
|
||||
function checkScriptInfoContents(contentsOfInfo: string, captionForContents: string) {
|
||||
const snap = info.getSnapshot();
|
||||
assert.equal(snap.getText(0, snap.getLength()), contentsOfInfo, "content should be equal to " + captionForContents);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Inferred projects", () => {
|
||||
@@ -4293,6 +4383,74 @@ namespace ts.projectSystem {
|
||||
checkNumberOfConfiguredProjects(service, 1);
|
||||
checkNumberOfInferredProjects(service, 0);
|
||||
});
|
||||
|
||||
it("should use projectRootPath when searching for inferred project again", () => {
|
||||
const projectDir = "/a/b/projects/project";
|
||||
const configFileLocation = `${projectDir}/src`;
|
||||
const f1 = {
|
||||
path: `${configFileLocation}/file1.ts`,
|
||||
content: ""
|
||||
};
|
||||
const configFile = {
|
||||
path: `${configFileLocation}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const configFile2 = {
|
||||
path: "/a/b/projects/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost([f1, libFile, configFile, configFile2]);
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
assert.isDefined(service.configuredProjects.get(configFile.path));
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
const typeRootLocations = getTypeRootsFromLocation(configFileLocation);
|
||||
checkWatchedDirectories(host, typeRootLocations.concat(configFileLocation), /*recursive*/ true);
|
||||
|
||||
// Delete config file - should create inferred project and not configured project
|
||||
host.reloadFS([f1, libFile, configFile2]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkNumberOfProjects(service, { inferredProjects: 1 });
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
checkWatchedDirectories(host, typeRootLocations, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("should use projectRootPath when searching for inferred project again 2", () => {
|
||||
const projectDir = "/a/b/projects/project";
|
||||
const configFileLocation = `${projectDir}/src`;
|
||||
const f1 = {
|
||||
path: `${configFileLocation}/file1.ts`,
|
||||
content: ""
|
||||
};
|
||||
const configFile = {
|
||||
path: `${configFileLocation}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const configFile2 = {
|
||||
path: "/a/b/projects/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const host = createServerHost([f1, libFile, configFile, configFile2]);
|
||||
const service = createProjectService(host, { useSingleInferredProject: true }, { useInferredProjectPerProjectRoot: true });
|
||||
service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir);
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
assert.isDefined(service.configuredProjects.get(configFile.path));
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
checkWatchedDirectories(host, getTypeRootsFromLocation(configFileLocation).concat(configFileLocation), /*recursive*/ true);
|
||||
|
||||
// Delete config file - should create inferred project with project root path set
|
||||
host.reloadFS([f1, libFile, configFile2]);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkNumberOfProjects(service, { inferredProjects: 1 });
|
||||
assert.equal(service.inferredProjects[0].projectRootPath, projectDir);
|
||||
checkWatchedFiles(host, [libFile.path, configFile.path, `${configFileLocation}/jsconfig.json`, `${projectDir}/tsconfig.json`, `${projectDir}/jsconfig.json`]);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
checkWatchedDirectories(host, getTypeRootsFromLocation(projectDir), /*recursive*/ true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancellationToken", () => {
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
/// <reference path="harness.ts" />
|
||||
|
||||
namespace ts.TestFSWithWatch {
|
||||
const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO);
|
||||
export const libFile: FileOrFolder = {
|
||||
path: "/a/lib/lib.d.ts",
|
||||
content: libFileContent
|
||||
content: `/// <reference no-default-lib="true"/>
|
||||
interface Boolean {}
|
||||
interface Function {}
|
||||
interface IArguments {}
|
||||
interface Number { toExponential: any; }
|
||||
interface Object {}
|
||||
interface RegExp {}
|
||||
interface String { charAt: any; }
|
||||
interface Array<T> {}`
|
||||
};
|
||||
|
||||
export const safeList = {
|
||||
|
||||
@@ -352,9 +352,9 @@ namespace ts.server {
|
||||
*/
|
||||
readonly configuredProjects = createMap<ConfiguredProject>();
|
||||
/**
|
||||
* list of open files
|
||||
* Open files: with value being project root path, and key being Path of the file that is open
|
||||
*/
|
||||
readonly openFiles: ScriptInfo[] = [];
|
||||
readonly openFiles = createMap<NormalizedPath>();
|
||||
|
||||
private compilerOptionsForInferredProjects: CompilerOptions;
|
||||
private compilerOptionsForInferredProjectsPerProjectRoot = createMap<CompilerOptions>();
|
||||
@@ -582,7 +582,7 @@ namespace ts.server {
|
||||
const event: ProjectsUpdatedInBackgroundEvent = {
|
||||
eventName: ProjectsUpdatedInBackgroundEvent,
|
||||
data: {
|
||||
openFiles: this.openFiles.map(f => f.fileName)
|
||||
openFiles: arrayFrom(this.openFiles.keys(), path => this.getScriptInfoForPath(path as Path).fileName)
|
||||
}
|
||||
};
|
||||
this.eventHandler(event);
|
||||
@@ -891,7 +891,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
assignOrphanScriptInfoToInferredProject(info: ScriptInfo, projectRootPath?: string) {
|
||||
assignOrphanScriptInfoToInferredProject(info: ScriptInfo, projectRootPath: NormalizedPath | undefined) {
|
||||
Debug.assert(info.isOrphan());
|
||||
|
||||
const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) ||
|
||||
@@ -935,7 +935,7 @@ namespace ts.server {
|
||||
info.close();
|
||||
this.stopWatchingConfigFilesForClosedScriptInfo(info);
|
||||
|
||||
unorderedRemoveItem(this.openFiles, info);
|
||||
this.openFiles.delete(info.path);
|
||||
|
||||
const fileExists = this.host.fileExists(info.fileName);
|
||||
|
||||
@@ -974,11 +974,12 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// collect orphaned files and assign them to inferred project just like we treat open of a file
|
||||
for (const f of this.openFiles) {
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
const f = this.getScriptInfoForPath(path as Path);
|
||||
if (f.isOrphan()) {
|
||||
this.assignOrphanScriptInfoToInferredProject(f);
|
||||
this.assignOrphanScriptInfoToInferredProject(f, projectRootPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
|
||||
// is postponed to next file open so that if file from same project is opened,
|
||||
@@ -1172,7 +1173,7 @@ namespace ts.server {
|
||||
* This is called by inferred project whenever script info is added as a root
|
||||
*/
|
||||
/* @internal */
|
||||
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) {
|
||||
startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, projectRootPath: NormalizedPath | undefined) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => {
|
||||
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
@@ -1194,7 +1195,7 @@ namespace ts.server {
|
||||
!this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) {
|
||||
this.createConfigFileWatcherOfConfigFileExistence(configFileName, canonicalConfigFilePath, configFileExistenceInfo);
|
||||
}
|
||||
});
|
||||
}, projectRootPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1262,7 +1263,7 @@ namespace ts.server {
|
||||
* The server must start searching from the directory containing
|
||||
* the newly opened file.
|
||||
*/
|
||||
private getConfigFileNameForFile(info: ScriptInfo, projectRootPath?: NormalizedPath) {
|
||||
private getConfigFileNameForFile(info: ScriptInfo, projectRootPath: NormalizedPath | undefined) {
|
||||
Debug.assert(info.isScriptOpen());
|
||||
this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`);
|
||||
const configFileName = this.forEachConfigFileLocation(info,
|
||||
@@ -1301,9 +1302,9 @@ namespace ts.server {
|
||||
printProjects(this.inferredProjects, counter);
|
||||
|
||||
this.logger.info("Open files: ");
|
||||
for (const rootFile of this.openFiles) {
|
||||
this.logger.info(`\t${rootFile.fileName}`);
|
||||
}
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
this.logger.info(`\tFileName: ${this.getScriptInfoForPath(path as Path).fileName} ProjectRootPath: ${projectRootPath}`);
|
||||
});
|
||||
|
||||
this.logger.endGroup();
|
||||
}
|
||||
@@ -1605,7 +1606,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private getOrCreateInferredProjectForProjectRootPathIfEnabled(info: ScriptInfo, projectRootPath: string | undefined): InferredProject | undefined {
|
||||
private getOrCreateInferredProjectForProjectRootPathIfEnabled(info: ScriptInfo, projectRootPath: NormalizedPath | undefined): InferredProject | undefined {
|
||||
if (!this.useInferredProjectPerProjectRoot) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -1659,7 +1660,7 @@ namespace ts.server {
|
||||
return this.createInferredProject(/*currentDirectory*/ undefined, /*isSingleInferredProject*/ true);
|
||||
}
|
||||
|
||||
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject {
|
||||
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
|
||||
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
|
||||
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory);
|
||||
if (isSingleInferredProject) {
|
||||
@@ -1796,23 +1797,19 @@ namespace ts.server {
|
||||
// as there is no need to load contents of the files from the disk
|
||||
|
||||
// Reload Projects
|
||||
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false);
|
||||
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue);
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
|
||||
private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) {
|
||||
// Get open files to reload projects for
|
||||
const openFiles = mapDefinedIter(
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile.entries(),
|
||||
([path, isRootOfInferredProject]) => {
|
||||
if (!ignoreIfNotRootOfInferredProject || isRootOfInferredProject) {
|
||||
const info = this.getScriptInfoForPath(path as Path);
|
||||
Debug.assert(!!info);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
this.reloadConfiguredProjectForFiles(
|
||||
configFileExistenceInfo.openFilesImpactedByConfigFile,
|
||||
/*delayReload*/ true,
|
||||
ignoreIfNotRootOfInferredProject ?
|
||||
isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project
|
||||
returnTrue // Reload all the open files impacted by config file
|
||||
);
|
||||
this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true);
|
||||
this.delayInferredProjectsRefresh();
|
||||
}
|
||||
|
||||
@@ -1821,16 +1818,24 @@ namespace ts.server {
|
||||
* If the config file is found and it refers to existing project, it reloads it either immediately
|
||||
* or schedules it for reload depending on delayReload option
|
||||
* If the there is no existing project it just opens the configured project for the config file
|
||||
* reloadForInfo provides a way to filter out files to reload configured project for
|
||||
*/
|
||||
private reloadConfiguredProjectForFiles(openFiles: ReadonlyArray<ScriptInfo>, delayReload: boolean) {
|
||||
private reloadConfiguredProjectForFiles<T>(openFiles: Map<T>, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean) {
|
||||
const updatedProjects = createMap<true>();
|
||||
// try to reload config file for all open files
|
||||
for (const info of openFiles) {
|
||||
openFiles.forEach((openFileValue, path) => {
|
||||
// Filter out the files that need to be ignored
|
||||
if (!shouldReloadProjectFor(openFileValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = this.getScriptInfoForPath(path as Path);
|
||||
Debug.assert(info.isScriptOpen());
|
||||
// This tries to search for a tsconfig.json for the given file. If we found it,
|
||||
// we first detect if there is already a configured project created for it: if so,
|
||||
// we re- read the tsconfig file content and update the project only if we havent already done so
|
||||
// otherwise we create a new one.
|
||||
const configFileName = this.getConfigFileNameForFile(info);
|
||||
const configFileName = this.getConfigFileNameForFile(info, this.openFiles.get(path));
|
||||
if (configFileName) {
|
||||
const project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
@@ -1848,7 +1853,7 @@ namespace ts.server {
|
||||
updatedProjects.set(configFileName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1893,16 +1898,17 @@ namespace ts.server {
|
||||
this.logger.info("refreshInferredProjects: updating project structure from ...");
|
||||
this.printProjects();
|
||||
|
||||
for (const info of this.openFiles) {
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
const info = this.getScriptInfoForPath(path as Path);
|
||||
// collect all orphaned script infos from open files
|
||||
if (info.isOrphan()) {
|
||||
this.assignOrphanScriptInfoToInferredProject(info);
|
||||
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
|
||||
}
|
||||
else {
|
||||
// Or remove the root of inferred project if is referenced in more than one projects
|
||||
this.removeRootOfInferredProjectIfNowPartOfOtherProject(info);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const p of this.inferredProjects) {
|
||||
p.updateGraph();
|
||||
@@ -1956,7 +1962,7 @@ namespace ts.server {
|
||||
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
|
||||
}
|
||||
Debug.assert(!info.isOrphan());
|
||||
this.openFiles.push(info);
|
||||
this.openFiles.set(info.path, projectRootPath);
|
||||
|
||||
if (sendConfigFileDiagEvent) {
|
||||
configFileErrors = project.getAllProjectErrors();
|
||||
|
||||
+3
-12
@@ -946,16 +946,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
reloadScript(filename: NormalizedPath, tempFileName?: NormalizedPath): boolean {
|
||||
const script = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (script) {
|
||||
Debug.assert(script.isAttached(this));
|
||||
script.reloadFromFile(tempFileName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
|
||||
this.updateGraph();
|
||||
@@ -1064,7 +1054,7 @@ namespace ts.server {
|
||||
projectService: ProjectService,
|
||||
documentRegistry: DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
projectRootPath: string | undefined,
|
||||
projectRootPath: NormalizedPath | undefined,
|
||||
currentDirectory: string | undefined) {
|
||||
super(InferredProject.newName(),
|
||||
ProjectKind.Inferred,
|
||||
@@ -1080,7 +1070,8 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
addRoot(info: ScriptInfo) {
|
||||
this.projectService.startWatchingConfigFilesForInferredProjectRoot(info);
|
||||
Debug.assert(info.isScriptOpen());
|
||||
this.projectService.startWatchingConfigFilesForInferredProjectRoot(info, this.projectService.openFiles.get(info.path));
|
||||
if (!this._isJsInferredProject && info.isJavaScript()) {
|
||||
this.toggleJsInferredProject(/*isJsInferredProject*/ true);
|
||||
}
|
||||
|
||||
+15
-1
@@ -1625,7 +1625,12 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* Names of one or more entries for which to obtain details.
|
||||
*/
|
||||
entryNames: string[];
|
||||
entryNames: (string | CompletionEntryIdentifier)[];
|
||||
}
|
||||
|
||||
export interface CompletionEntryIdentifier {
|
||||
name: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1685,6 +1690,10 @@ namespace ts.server.protocol {
|
||||
* made to avoid errors. The CompletionEntryDetails will have these actions.
|
||||
*/
|
||||
hasAction?: true;
|
||||
/**
|
||||
* Identifier (not necessarily human-readable) identifying where this completion came from.
|
||||
*/
|
||||
source?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1722,6 +1731,11 @@ namespace ts.server.protocol {
|
||||
* The associated code actions for this entry
|
||||
*/
|
||||
codeActions?: CodeAction[];
|
||||
|
||||
/**
|
||||
* Human-readable description of the `source` from the CompletionEntry.
|
||||
*/
|
||||
source?: SymbolDisplayPart[];
|
||||
}
|
||||
|
||||
export interface CompletionsResponse extends Response {
|
||||
|
||||
+24
-22
@@ -67,7 +67,10 @@ namespace ts.server {
|
||||
this.lineMap = undefined;
|
||||
}
|
||||
|
||||
/** returns true if text changed */
|
||||
/**
|
||||
* Set the contents as newText
|
||||
* returns true if text changed
|
||||
*/
|
||||
public reload(newText: string) {
|
||||
Debug.assert(newText !== undefined);
|
||||
|
||||
@@ -87,31 +90,31 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/** returns true if text changed */
|
||||
public reloadFromDisk() {
|
||||
let reloaded = false;
|
||||
if (!this.pendingReloadFromDisk && !this.ownFileText) {
|
||||
reloaded = this.reload(this.getFileText());
|
||||
this.ownFileText = true;
|
||||
}
|
||||
/**
|
||||
* Reads the contents from tempFile(if supplied) or own file and sets it as contents
|
||||
* returns true if text changed
|
||||
*/
|
||||
public reloadWithFileText(tempFileName?: string) {
|
||||
const reloaded = this.reload(this.getFileText(tempFileName));
|
||||
this.ownFileText = !tempFileName || tempFileName === this.fileName;
|
||||
return reloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the contents from the file if there is no pending reload from disk or the contents of file are same as file text
|
||||
* returns true if text changed
|
||||
*/
|
||||
public reloadFromDisk() {
|
||||
if (!this.pendingReloadFromDisk && !this.ownFileText) {
|
||||
return this.reloadWithFileText();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public delayReloadFromFileIntoText() {
|
||||
this.pendingReloadFromDisk = true;
|
||||
}
|
||||
|
||||
/** returns true if text changed */
|
||||
public reloadFromFile(tempFileName: string) {
|
||||
let reloaded = false;
|
||||
// Reload if different file or we dont know if we are working with own file text
|
||||
if (tempFileName !== this.fileName || !this.ownFileText) {
|
||||
reloaded = this.reload(this.getFileText(tempFileName));
|
||||
this.ownFileText = !tempFileName || tempFileName === this.fileName;
|
||||
}
|
||||
return reloaded;
|
||||
}
|
||||
|
||||
public getSnapshot(): IScriptSnapshot {
|
||||
return this.useScriptVersionCacheIfValidOrOpen()
|
||||
? this.svc.getSnapshot()
|
||||
@@ -180,8 +183,7 @@ namespace ts.server {
|
||||
private getOrLoadText() {
|
||||
if (this.text === undefined || this.pendingReloadFromDisk) {
|
||||
Debug.assert(!this.svc || this.pendingReloadFromDisk, "ScriptVersionCache should not be set when reloading from disk");
|
||||
this.reload(this.getFileText());
|
||||
this.ownFileText = true;
|
||||
this.reloadWithFileText();
|
||||
}
|
||||
return this.text;
|
||||
}
|
||||
@@ -385,7 +387,7 @@ namespace ts.server {
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
else {
|
||||
if (this.textStorage.reloadFromFile(tempFileName)) {
|
||||
if (this.textStorage.reloadWithFileText(tempFileName)) {
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -1188,10 +1188,10 @@ namespace ts.server {
|
||||
if (simplifiedResult) {
|
||||
return mapDefined<CompletionEntry, protocol.CompletionEntry>(completions && completions.entries, entry => {
|
||||
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction } = entry;
|
||||
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source } = entry;
|
||||
const convertedSpan = replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
|
||||
// Use `hasAction || undefined` to avoid serializing `false`.
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined };
|
||||
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan, hasAction: hasAction || undefined, source };
|
||||
}
|
||||
}).sort((a, b) => compareStrings(a.name, b.name));
|
||||
}
|
||||
@@ -1206,8 +1206,9 @@ namespace ts.server {
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
const formattingOptions = project.projectService.getFormatCodeOptions(file);
|
||||
|
||||
return mapDefined(args.entryNames, entryName => {
|
||||
const details = project.getLanguageService().getCompletionEntryDetails(file, position, entryName, formattingOptions);
|
||||
return mapDefined<string | protocol.CompletionEntryIdentifier, protocol.CompletionEntryDetails>(args.entryNames, entryName => {
|
||||
const { name, source } = typeof entryName === "string" ? { name: entryName, source: undefined } : entryName;
|
||||
const details = project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source);
|
||||
if (details) {
|
||||
const mappedCodeActions = map(details.codeActions, action => this.mapCodeAction(action, scriptInfo));
|
||||
return { ...details, codeActions: mappedCodeActions };
|
||||
@@ -1311,11 +1312,13 @@ namespace ts.server {
|
||||
private reload(args: protocol.ReloadRequestArgs, reqSeq: number) {
|
||||
const file = toNormalizedPath(args.file);
|
||||
const tempFileName = args.tmpfile && toNormalizedPath(args.tmpfile);
|
||||
const project = this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ true);
|
||||
this.changeSeq++;
|
||||
// make sure no changes happen before this one is finished
|
||||
if (project.reloadScript(file, tempFileName)) {
|
||||
this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true);
|
||||
const info = this.projectService.getScriptInfoForNormalizedPath(file);
|
||||
if (info) {
|
||||
this.changeSeq++;
|
||||
// make sure no changes happen before this one is finished
|
||||
if (info.reloadFromFile(tempFileName)) {
|
||||
this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,11 @@ namespace ts.codefix {
|
||||
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
|
||||
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
|
||||
const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClauseOfKind(kind, symbolName), createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes));
|
||||
const importDecl = createImportDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
createImportClauseOfKind(kind, symbolName),
|
||||
createStringLiteralWithQuoteStyle(sourceFile, moduleSpecifierWithoutQuotes));
|
||||
const changes = ChangeTracker.with(context, changeTracker => {
|
||||
if (lastImportDeclaration) {
|
||||
changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: newLineCharacter });
|
||||
@@ -279,13 +283,14 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
function createImportClauseOfKind(kind: ImportKind, symbolName: string) {
|
||||
const id = createIdentifier(symbolName);
|
||||
switch (kind) {
|
||||
case ImportKind.Default:
|
||||
return createImportClause(createIdentifier(symbolName), /*namedBindings*/ undefined);
|
||||
return createImportClause(id, /*namedBindings*/ undefined);
|
||||
case ImportKind.Namespace:
|
||||
return createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(symbolName)));
|
||||
return createImportClause(/*name*/ undefined, createNamespaceImport(id));
|
||||
case ImportKind.Named:
|
||||
return createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, createIdentifier(symbolName))]));
|
||||
return createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, id)]));
|
||||
default:
|
||||
Debug.assertNever(kind);
|
||||
}
|
||||
@@ -529,7 +534,7 @@ namespace ts.codefix {
|
||||
declarations: ReadonlyArray<AnyImportSyntax>): ImportCodeAction {
|
||||
const fromExistingImport = firstDefined(declarations, declaration => {
|
||||
if (declaration.kind === SyntaxKind.ImportDeclaration && declaration.importClause) {
|
||||
const changes = tryUpdateExistingImport(ctx, ctx.kind, declaration.importClause);
|
||||
const changes = tryUpdateExistingImport(ctx, declaration.importClause);
|
||||
if (changes) {
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(declaration.moduleSpecifier.getText());
|
||||
return createCodeAction(
|
||||
@@ -559,8 +564,8 @@ namespace ts.codefix {
|
||||
return expression && isStringLiteral(expression) ? expression.text : undefined;
|
||||
}
|
||||
|
||||
function tryUpdateExistingImport(context: SymbolContext, kind: ImportKind, importClause: ImportClause): FileTextChanges[] | undefined {
|
||||
const { symbolName, sourceFile } = context;
|
||||
function tryUpdateExistingImport(context: SymbolContext & { kind: ImportKind }, importClause: ImportClause): FileTextChanges[] | undefined {
|
||||
const { symbolName, sourceFile, kind } = context;
|
||||
const { name, namedBindings } = importClause;
|
||||
switch (kind) {
|
||||
case ImportKind.Default:
|
||||
|
||||
+132
-69
@@ -12,7 +12,7 @@ namespace ts.Completions {
|
||||
* Map from symbol id -> SymbolOriginInfo.
|
||||
* Only populated for symbols that come from other modules.
|
||||
*/
|
||||
type SymbolOriginInfoMap = SymbolOriginInfo[];
|
||||
type SymbolOriginInfoMap = (SymbolOriginInfo | undefined)[];
|
||||
|
||||
const enum KeywordCompletionFilters {
|
||||
None,
|
||||
@@ -100,7 +100,7 @@ namespace ts.Completions {
|
||||
function getJavaScriptCompletionEntries(
|
||||
sourceFile: SourceFile,
|
||||
position: number,
|
||||
uniqueNames: Map<true>,
|
||||
uniqueNames: Map<{}>,
|
||||
target: ScriptTarget,
|
||||
entries: Push<CompletionEntry>): void {
|
||||
getNameTable(sourceFile).forEach((pos, name) => {
|
||||
@@ -127,7 +127,15 @@ namespace ts.Completions {
|
||||
});
|
||||
}
|
||||
|
||||
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget, allowStringLiteral: boolean): CompletionEntry {
|
||||
function createCompletionEntry(
|
||||
symbol: Symbol,
|
||||
location: Node,
|
||||
performCharacterChecks: boolean,
|
||||
typeChecker: TypeChecker,
|
||||
target: ScriptTarget,
|
||||
allowStringLiteral: boolean,
|
||||
origin: SymbolOriginInfo,
|
||||
): CompletionEntry | undefined {
|
||||
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
|
||||
// We would like to only show things that can be added after a dot, so for instance numeric properties can
|
||||
// not be accessed with a dot (a.1 <- invalid)
|
||||
@@ -149,9 +157,15 @@ namespace ts.Completions {
|
||||
kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location),
|
||||
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
|
||||
sortText: "0",
|
||||
source: getSourceFromOrigin(origin),
|
||||
hasAction: origin === undefined ? undefined : true,
|
||||
};
|
||||
}
|
||||
|
||||
function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined {
|
||||
return origin && stripQuotes(origin.moduleSymbol.name);
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromSymbols(
|
||||
symbols: ReadonlyArray<Symbol>,
|
||||
entries: Push<CompletionEntry>,
|
||||
@@ -164,25 +178,35 @@ namespace ts.Completions {
|
||||
symbolToOriginInfoMap?: SymbolOriginInfoMap,
|
||||
): Map<true> {
|
||||
const start = timestamp();
|
||||
const uniqueNames = createMap<true>();
|
||||
// Tracks unique names.
|
||||
// We don't set this for global variables or completions from external module exports, because we can have multiple of those.
|
||||
// Based on the order we add things we will always see locals first, then globals, then module exports.
|
||||
// So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name.
|
||||
const uniques = createMap<true>();
|
||||
if (symbols) {
|
||||
for (const symbol of symbols) {
|
||||
const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, allowStringLiteral);
|
||||
if (entry) {
|
||||
const id = entry.name;
|
||||
if (!uniqueNames.has(id)) {
|
||||
if (symbolToOriginInfoMap && symbolToOriginInfoMap[getUniqueSymbolId(symbol, typeChecker)]) {
|
||||
entry.hasAction = true;
|
||||
}
|
||||
entries.push(entry);
|
||||
uniqueNames.set(id, true);
|
||||
}
|
||||
const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined;
|
||||
const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, allowStringLiteral, origin);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name } = entry;
|
||||
if (uniques.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Latter case tests whether this is a global variable.
|
||||
if (!origin && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile()))) {
|
||||
uniques.set(name, true);
|
||||
}
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start));
|
||||
return uniqueNames;
|
||||
return uniques;
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined {
|
||||
@@ -329,59 +353,104 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
function getSymbolCompletionFromEntryId(
|
||||
typeChecker: TypeChecker,
|
||||
log: (message: string) => void,
|
||||
compilerOptions: CompilerOptions,
|
||||
sourceFile: SourceFile,
|
||||
position: number,
|
||||
{ name, source }: CompletionEntryIdentifier,
|
||||
allSourceFiles: ReadonlyArray<SourceFile>,
|
||||
): { type: "symbol", symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | { type: "request", request: Request } | { type: "none" } {
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
|
||||
if (!completionData) {
|
||||
return { type: "none" };
|
||||
}
|
||||
|
||||
const { symbols, location, allowStringLiteral, symbolToOriginInfoMap, request } = completionData;
|
||||
if (request) {
|
||||
return { type: "request", request };
|
||||
}
|
||||
|
||||
// Find the symbol with the matching entry name.
|
||||
// We don't need to perform character checks here because we're only comparing the
|
||||
// name against 'entryName' (which is known to be good), not building a new
|
||||
// completion entry.
|
||||
const symbol = find(symbols, s =>
|
||||
getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === name
|
||||
&& getSourceFromOrigin(symbolToOriginInfoMap[getSymbolId(s)]) === source);
|
||||
return symbol ? { type: "symbol", symbol, location, symbolToOriginInfoMap } : { type: "none" };
|
||||
}
|
||||
|
||||
export interface CompletionEntryIdentifier {
|
||||
name: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export function getCompletionEntryDetails(
|
||||
typeChecker: TypeChecker,
|
||||
log: (message: string) => void,
|
||||
compilerOptions: CompilerOptions,
|
||||
sourceFile: SourceFile,
|
||||
position: number,
|
||||
name: string,
|
||||
entryId: CompletionEntryIdentifier,
|
||||
allSourceFiles: ReadonlyArray<SourceFile>,
|
||||
host: LanguageServiceHost,
|
||||
rulesProvider: formatting.RulesProvider,
|
||||
): CompletionEntryDetails {
|
||||
|
||||
const { name, source } = entryId;
|
||||
// Compute all the completion symbols again.
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
|
||||
if (completionData) {
|
||||
const { symbols, location, allowStringLiteral, symbolToOriginInfoMap } = completionData;
|
||||
|
||||
// Find the symbol with the matching entry name.
|
||||
// We don't need to perform character checks here because we're only comparing the
|
||||
// name against 'entryName' (which is known to be good), not building a new
|
||||
// completion entry.
|
||||
const symbol = find(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === name);
|
||||
|
||||
if (symbol) {
|
||||
const symbolCompletion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles);
|
||||
switch (symbolCompletion.type) {
|
||||
case "request": {
|
||||
const { request } = symbolCompletion;
|
||||
switch (request.kind) {
|
||||
case "JsDocTagName":
|
||||
return JsDoc.getJSDocTagNameCompletionDetails(name);
|
||||
case "JsDocTag":
|
||||
return JsDoc.getJSDocTagCompletionDetails(name);
|
||||
case "JsDocParameterName":
|
||||
return JsDoc.getJSDocParameterNameCompletionDetails(name);
|
||||
default:
|
||||
return Debug.assertNever(request);
|
||||
}
|
||||
}
|
||||
case "symbol": {
|
||||
const { symbol, location, symbolToOriginInfoMap } = symbolCompletion;
|
||||
const codeActions = getCompletionEntryCodeActions(symbolToOriginInfoMap, symbol, typeChecker, host, compilerOptions, sourceFile, rulesProvider);
|
||||
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
|
||||
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
|
||||
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions };
|
||||
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: source === undefined ? undefined : [textPart(source)] };
|
||||
}
|
||||
case "none": {
|
||||
// Didn't find a symbol with this name. See if we can find a keyword instead.
|
||||
if (some(getKeywordCompletions(KeywordCompletionFilters.None), c => c.name === name)) {
|
||||
return {
|
||||
name,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
displayParts: [displayPart(name, SymbolDisplayPartKind.keyword)],
|
||||
documentation: undefined,
|
||||
tags: undefined,
|
||||
codeActions: undefined,
|
||||
source: undefined,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find a symbol with this name. See if we can find a keyword instead.
|
||||
const keywordCompletion = forEach(
|
||||
getKeywordCompletions(KeywordCompletionFilters.None),
|
||||
c => c.name === name
|
||||
);
|
||||
if (keywordCompletion) {
|
||||
return {
|
||||
name,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
displayParts: [displayPart(name, SymbolDisplayPartKind.keyword)],
|
||||
documentation: undefined,
|
||||
tags: undefined,
|
||||
codeActions: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getCompletionEntryCodeActions(symbolToOriginInfoMap: SymbolOriginInfoMap, symbol: Symbol, checker: TypeChecker, host: LanguageServiceHost, compilerOptions: CompilerOptions, sourceFile: SourceFile, rulesProvider: formatting.RulesProvider): CodeAction[] | undefined {
|
||||
const symbolOriginInfo = symbolToOriginInfoMap[getUniqueSymbolId(symbol, checker)];
|
||||
function getCompletionEntryCodeActions(
|
||||
symbolToOriginInfoMap: SymbolOriginInfoMap,
|
||||
symbol: Symbol,
|
||||
checker: TypeChecker,
|
||||
host: LanguageServiceHost,
|
||||
compilerOptions: CompilerOptions,
|
||||
sourceFile: SourceFile,
|
||||
rulesProvider: formatting.RulesProvider,
|
||||
): CodeAction[] | undefined {
|
||||
const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)];
|
||||
if (!symbolOriginInfo) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -407,29 +476,20 @@ namespace ts.Completions {
|
||||
compilerOptions: CompilerOptions,
|
||||
sourceFile: SourceFile,
|
||||
position: number,
|
||||
entryName: string,
|
||||
entryId: CompletionEntryIdentifier,
|
||||
allSourceFiles: ReadonlyArray<SourceFile>,
|
||||
): Symbol | undefined {
|
||||
// Compute all the completion symbols again.
|
||||
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
|
||||
if (!completionData) {
|
||||
return undefined;
|
||||
}
|
||||
const { symbols, allowStringLiteral } = completionData;
|
||||
// Find the symbol with the matching entry name.
|
||||
// We don't need to perform character checks here because we're only comparing the
|
||||
// name against 'entryName' (which is known to be good), not building a new
|
||||
// completion entry.
|
||||
return find(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === entryName);
|
||||
const completion = getSymbolCompletionFromEntryId(typeChecker, log, compilerOptions, sourceFile, position, entryId, allSourceFiles);
|
||||
return completion.type === "symbol" ? completion.symbol : undefined;
|
||||
}
|
||||
|
||||
interface CompletionData {
|
||||
symbols: Symbol[];
|
||||
symbols: ReadonlyArray<Symbol>;
|
||||
isGlobalCompletion: boolean;
|
||||
isMemberCompletion: boolean;
|
||||
allowStringLiteral: boolean;
|
||||
isNewIdentifierLocation: boolean;
|
||||
location: Node;
|
||||
location: Node | undefined;
|
||||
isRightOfDot: boolean;
|
||||
request?: Request;
|
||||
keywordFilters: KeywordCompletionFilters;
|
||||
@@ -516,7 +576,7 @@ namespace ts.Completions {
|
||||
|
||||
if (request) {
|
||||
return {
|
||||
symbols: undefined,
|
||||
symbols: emptyArray,
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
allowStringLiteral: false,
|
||||
@@ -923,7 +983,6 @@ namespace ts.Completions {
|
||||
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string): void {
|
||||
const tokenTextLowerCase = tokenText.toLowerCase();
|
||||
const symbolIdMap = arrayToNumericMap(symbols, s => getUniqueSymbolId(s, typeChecker));
|
||||
|
||||
codefix.forEachExternalModule(typeChecker, allSourceFiles, moduleSymbol => {
|
||||
if (moduleSymbol === sourceFile.symbol) {
|
||||
@@ -941,10 +1000,14 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
const id = getUniqueSymbolId(symbol, typeChecker);
|
||||
if (!symbolIdMap[id] && stringContainsCharactersInOrder(name.toLowerCase(), tokenTextLowerCase)) {
|
||||
if (symbol.declarations && symbol.declarations.some(d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) {
|
||||
// Don't add a completion for a re-export, only for the original.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stringContainsCharactersInOrder(name.toLowerCase(), tokenTextLowerCase)) {
|
||||
symbols.push(symbol);
|
||||
symbolToOriginInfoMap[id] = { moduleSymbol, isDefaultExport };
|
||||
symbolToOriginInfoMap[getSymbolId(symbol)] = { moduleSymbol, isDefaultExport };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace ts.JsDoc {
|
||||
}));
|
||||
}
|
||||
|
||||
export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails;
|
||||
|
||||
export function getJSDocTagCompletions(): CompletionEntry[] {
|
||||
return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = ts.map(jsDocTagNames, tagName => {
|
||||
return {
|
||||
@@ -119,6 +121,18 @@ namespace ts.JsDoc {
|
||||
}));
|
||||
}
|
||||
|
||||
export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails {
|
||||
return {
|
||||
name,
|
||||
kind: ScriptElementKind.unknown, // TODO: should have its own kind?
|
||||
kindModifiers: "",
|
||||
displayParts: [textPart(name)],
|
||||
documentation: emptyArray,
|
||||
tags: emptyArray,
|
||||
codeActions: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] {
|
||||
if (!isIdentifier(tag.name)) {
|
||||
return emptyArray;
|
||||
@@ -141,6 +155,18 @@ namespace ts.JsDoc {
|
||||
});
|
||||
}
|
||||
|
||||
export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails {
|
||||
return {
|
||||
name,
|
||||
kind: ScriptElementKind.parameterElement,
|
||||
kindModifiers: "",
|
||||
displayParts: [textPart(name)],
|
||||
documentation: emptyArray,
|
||||
tags: emptyArray,
|
||||
codeActions: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if position points to a valid position to add JSDoc comments, and if so,
|
||||
* returns the appropriate template. Otherwise returns an empty string.
|
||||
|
||||
@@ -244,12 +244,52 @@ namespace ts.refactor.extractSymbol {
|
||||
return { targetRange: { range: statements, facts: rangeFacts, declarations } };
|
||||
}
|
||||
|
||||
if (isReturnStatement(start) && !start.expression) {
|
||||
// Makes no sense to extract an expression-less return statement.
|
||||
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.CannotExtractRange)] };
|
||||
}
|
||||
|
||||
// We have a single node (start)
|
||||
const errors = checkRootNode(start) || checkNode(start);
|
||||
const node = refineNode(start);
|
||||
|
||||
const errors = checkRootNode(node) || checkNode(node);
|
||||
if (errors) {
|
||||
return { errors };
|
||||
}
|
||||
return { targetRange: { range: getStatementOrExpressionRange(start), facts: rangeFacts, declarations } };
|
||||
return { targetRange: { range: getStatementOrExpressionRange(node), facts: rangeFacts, declarations } };
|
||||
|
||||
/**
|
||||
* Attempt to refine the extraction node (generally, by shrinking it) to produce better results.
|
||||
* @param node The unrefined extraction node.
|
||||
*/
|
||||
function refineNode(node: Node) {
|
||||
if (isReturnStatement(node)) {
|
||||
if (node.expression) {
|
||||
return node.expression;
|
||||
}
|
||||
}
|
||||
else if (isVariableStatement(node)) {
|
||||
let numInitializers = 0;
|
||||
let lastInitializer: Expression | undefined = undefined;
|
||||
for (const declaration of node.declarationList.declarations) {
|
||||
if (declaration.initializer) {
|
||||
numInitializers++;
|
||||
lastInitializer = declaration.initializer;
|
||||
}
|
||||
}
|
||||
if (numInitializers === 1) {
|
||||
return lastInitializer;
|
||||
}
|
||||
// No special handling if there are multiple initializers.
|
||||
}
|
||||
else if (isVariableDeclaration(node)) {
|
||||
if (node.initializer) {
|
||||
return node.initializer;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function checkRootNode(node: Node): Diagnostic[] | undefined {
|
||||
if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) {
|
||||
|
||||
@@ -1327,16 +1327,31 @@ namespace ts {
|
||||
return Completions.getCompletionsAtPosition(host, program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, program.getSourceFiles());
|
||||
}
|
||||
|
||||
function getCompletionEntryDetails(fileName: string, position: number, entryName: string, formattingOptions?: FormatCodeSettings): CompletionEntryDetails {
|
||||
function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions?: FormatCodeSettings, source?: string): CompletionEntryDetails {
|
||||
synchronizeHostData();
|
||||
const ruleProvider = formattingOptions ? getRuleProvider(formattingOptions) : undefined;
|
||||
return Completions.getCompletionEntryDetails(
|
||||
program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, entryName, program.getSourceFiles(), host, ruleProvider);
|
||||
program.getTypeChecker(),
|
||||
log,
|
||||
program.getCompilerOptions(),
|
||||
getValidSourceFile(fileName),
|
||||
position,
|
||||
{ name, source },
|
||||
program.getSourceFiles(),
|
||||
host,
|
||||
ruleProvider);
|
||||
}
|
||||
|
||||
function getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol {
|
||||
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol {
|
||||
synchronizeHostData();
|
||||
return Completions.getCompletionEntrySymbol(program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, entryName, program.getSourceFiles());
|
||||
return Completions.getCompletionEntrySymbol(
|
||||
program.getTypeChecker(),
|
||||
log,
|
||||
program.getCompilerOptions(),
|
||||
getValidSourceFile(fileName),
|
||||
position,
|
||||
{ name, source },
|
||||
program.getSourceFiles());
|
||||
}
|
||||
|
||||
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {
|
||||
|
||||
@@ -237,9 +237,9 @@ namespace ts {
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
|
||||
// "options" is optional only for backwards-compatibility
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
|
||||
// "options" and "source" are optional only for backwards-compatibility
|
||||
getCompletionEntryDetails(fileName: string, position: number, name: string, options?: FormatCodeOptions | FormatCodeSettings, source?: string): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol;
|
||||
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
|
||||
|
||||
@@ -696,6 +696,7 @@ namespace ts {
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface CompletionEntryDetails {
|
||||
@@ -706,6 +707,7 @@ namespace ts {
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
source?: SymbolDisplayPart[];
|
||||
}
|
||||
|
||||
export interface OutliningSpan {
|
||||
|
||||
+22
-8
@@ -3915,8 +3915,8 @@ declare namespace ts {
|
||||
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
|
||||
getCompletionEntryDetails(fileName: string, position: number, name: string, options?: FormatCodeOptions | FormatCodeSettings, source?: string): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol;
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
|
||||
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
|
||||
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan;
|
||||
@@ -4296,6 +4296,7 @@ declare namespace ts {
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
source?: string;
|
||||
}
|
||||
interface CompletionEntryDetails {
|
||||
name: string;
|
||||
@@ -4305,6 +4306,7 @@ declare namespace ts {
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
source?: SymbolDisplayPart[];
|
||||
}
|
||||
interface OutliningSpan {
|
||||
/** The span of the document to actually collapse. */
|
||||
@@ -6031,7 +6033,11 @@ declare namespace ts.server.protocol {
|
||||
/**
|
||||
* Names of one or more entries for which to obtain details.
|
||||
*/
|
||||
entryNames: string[];
|
||||
entryNames: (string | CompletionEntryIdentifier)[];
|
||||
}
|
||||
interface CompletionEntryIdentifier {
|
||||
name: string;
|
||||
source: string;
|
||||
}
|
||||
/**
|
||||
* Completion entry details request; value of command field is
|
||||
@@ -6087,6 +6093,10 @@ declare namespace ts.server.protocol {
|
||||
* made to avoid errors. The CompletionEntryDetails will have these actions.
|
||||
*/
|
||||
hasAction?: true;
|
||||
/**
|
||||
* Identifier (not necessarily human-readable) identifying where this completion came from.
|
||||
*/
|
||||
source?: string;
|
||||
}
|
||||
/**
|
||||
* Additional completion entry details, available on demand
|
||||
@@ -6120,6 +6130,10 @@ declare namespace ts.server.protocol {
|
||||
* The associated code actions for this entry
|
||||
*/
|
||||
codeActions?: CodeAction[];
|
||||
/**
|
||||
* Human-readable description of the `source` from the CompletionEntry.
|
||||
*/
|
||||
source?: SymbolDisplayPart[];
|
||||
}
|
||||
interface CompletionsResponse extends Response {
|
||||
body?: CompletionEntry[];
|
||||
@@ -7227,7 +7241,6 @@ declare namespace ts.server {
|
||||
getScriptInfo(uncheckedFileName: string): ScriptInfo;
|
||||
filesToString(writeProjectFileNames: boolean): string;
|
||||
setCompilerOptions(compilerOptions: CompilerOptions): void;
|
||||
reloadScript(filename: NormalizedPath, tempFileName?: NormalizedPath): boolean;
|
||||
protected removeRoot(info: ScriptInfo): void;
|
||||
}
|
||||
/**
|
||||
@@ -7435,9 +7448,9 @@ declare namespace ts.server {
|
||||
*/
|
||||
readonly configuredProjects: Map<ConfiguredProject>;
|
||||
/**
|
||||
* list of open files
|
||||
* Open files: with value being project root path, and key being Path of the file that is open
|
||||
*/
|
||||
readonly openFiles: ScriptInfo[];
|
||||
readonly openFiles: Map<NormalizedPath>;
|
||||
private compilerOptionsForInferredProjects;
|
||||
private compilerOptionsForInferredProjectsPerProjectRoot;
|
||||
/**
|
||||
@@ -7556,7 +7569,7 @@ declare namespace ts.server {
|
||||
* The server must start searching from the directory containing
|
||||
* the newly opened file.
|
||||
*/
|
||||
private getConfigFileNameForFile(info, projectRootPath?);
|
||||
private getConfigFileNameForFile(info, projectRootPath);
|
||||
private printProjects();
|
||||
private findConfiguredProjectByProjectName(configFileName);
|
||||
private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath);
|
||||
@@ -7592,8 +7605,9 @@ declare namespace ts.server {
|
||||
* If the config file is found and it refers to existing project, it reloads it either immediately
|
||||
* or schedules it for reload depending on delayReload option
|
||||
* If the there is no existing project it just opens the configured project for the config file
|
||||
* reloadForInfo provides a way to filter out files to reload configured project for
|
||||
*/
|
||||
private reloadConfiguredProjectForFiles(openFiles, delayReload);
|
||||
private reloadConfiguredProjectForFiles<T>(openFiles, delayReload, shouldReloadProjectFor);
|
||||
/**
|
||||
* Remove the root of inferred project if script info is part of another project
|
||||
*/
|
||||
|
||||
+4
-2
@@ -3915,8 +3915,8 @@ declare namespace ts {
|
||||
getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
|
||||
getCompletionEntryDetails(fileName: string, position: number, name: string, options?: FormatCodeOptions | FormatCodeSettings, source?: string): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol;
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
|
||||
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
|
||||
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan;
|
||||
@@ -4296,6 +4296,7 @@ declare namespace ts {
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
source?: string;
|
||||
}
|
||||
interface CompletionEntryDetails {
|
||||
name: string;
|
||||
@@ -4305,6 +4306,7 @@ declare namespace ts {
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
source?: SymbolDisplayPart[];
|
||||
}
|
||||
interface OutliningSpan {
|
||||
/** The span of the document to actually collapse. */
|
||||
|
||||
@@ -26,7 +26,7 @@ interface UnaryExpression {
|
||||
function parseUnaryExpression(operator: string): UnaryExpression {
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
function newFunction(): UnaryExpression {
|
||||
return {
|
||||
kind: "Unary",
|
||||
operator,
|
||||
@@ -49,7 +49,7 @@ function parseUnaryExpression(operator: string): UnaryExpression {
|
||||
return /*RENAME*/newFunction(operator);
|
||||
}
|
||||
|
||||
function newFunction(operator: string) {
|
||||
function newFunction(operator: string): UnaryExpression {
|
||||
return {
|
||||
kind: "Unary",
|
||||
operator,
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace N {
|
||||
export const value = 1;
|
||||
|
||||
() => {
|
||||
/*RENAME*/newFunction();
|
||||
var c = /*RENAME*/newFunction()
|
||||
}
|
||||
|
||||
function newFunction() {
|
||||
var c = class {
|
||||
return class {
|
||||
M() {
|
||||
return value;
|
||||
}
|
||||
@@ -34,12 +34,12 @@ namespace N {
|
||||
export const value = 1;
|
||||
|
||||
() => {
|
||||
/*RENAME*/newFunction();
|
||||
var c = /*RENAME*/newFunction()
|
||||
}
|
||||
}
|
||||
|
||||
function newFunction() {
|
||||
var c = class {
|
||||
return class {
|
||||
M() {
|
||||
return N.value;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/const x = 1;/*|]*/
|
||||
/*[#|*/const x = 1;
|
||||
"hello";/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
const x = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/const x = 1;/*|]*/
|
||||
/*[#|*/const x = 1;
|
||||
"hello";/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
const x = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/const x: number = 1;/*|]*/
|
||||
/*[#|*/const x: number = 1;
|
||||
"hello";/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
const x: number = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/const x: number = 1;/*|]*/
|
||||
/*[#|*/const x: number = 1;
|
||||
"hello";/*|]*/
|
||||
x; x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x; x;
|
||||
|
||||
function newFunction() {
|
||||
const x: number = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/let x = 1;/*|]*/
|
||||
/*[#|*/let x = 1;
|
||||
"hello";/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
let x = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/let x = 1;/*|]*/
|
||||
/*[#|*/let x = 1;
|
||||
"hello";/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
let x = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/let x: number = 1;/*|]*/
|
||||
/*[#|*/let x: number = 1;
|
||||
"hello";/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
let x: number = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/var x = 1;/*|]*/
|
||||
/*[#|*/var x = 1;
|
||||
"hello"/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
var x = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// ==ORIGINAL==
|
||||
|
||||
/*[#|*/var x = 1;/*|]*/
|
||||
/*[#|*/var x = 1;
|
||||
"hello"/*|]*/
|
||||
x;
|
||||
|
||||
// ==SCOPE::Extract to function in global scope==
|
||||
@@ -10,5 +11,6 @@ x;
|
||||
|
||||
function newFunction() {
|
||||
var x = 1;
|
||||
"hello";
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Add 'foo' to existing import declaration from "./a".`,
|
||||
newFileContent: `import foo, { x } from "./a";
|
||||
f;`,
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Add 'foo' to existing import declaration from "./a".`,
|
||||
newFileContent: `import foo, * as a from "./a";
|
||||
f;`,
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import f_o_o from "./a";
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import foo from "./a";\r
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "x",
|
||||
source: "m",
|
||||
description: `Import 'x' from "m".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import { x } from "m";\r
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
goTo.marker("");
|
||||
|
||||
verify.not.completionListContains("abcde");
|
||||
verify.not.completionListContains("dbf");
|
||||
verify.not.completionListContains({ name: "abcde", source: "/a" });
|
||||
verify.not.completionListContains({ name: "dbf", source: "/a" });
|
||||
|
||||
verify.completionListContains("bdf", "function bdf(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains("abcdef", "function abcdef(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains("BDF", "function BDF(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "bdf", source: "/a" }, "function bdf(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "abcdef", source: "/a" }, "function abcdef(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "BDF", source: "/a" }, "function BDF(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /global.d.ts
|
||||
// A local variable would prevent import completions (see `completionsImport_shadowedByLocal.ts`), but a global doesn't.
|
||||
////declare var foo: number;
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export const foo = 0;
|
||||
|
||||
// @Filename: /b.ts
|
||||
////export const foo = 1;
|
||||
|
||||
// @Filename: /c.ts
|
||||
////fo/**/
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "var foo: number", "", "var");
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "const foo: 0", "", "const", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/b" }, "const foo: 1", "", "const", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/b",
|
||||
description: `Import 'foo' from "./b".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import { foo } from "./b";\r
|
||||
\r
|
||||
fo`,
|
||||
});
|
||||
@@ -9,10 +9,11 @@
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Add 'foo' to existing import declaration from "./a".`,
|
||||
newFileContent: `import { x, foo } from "./a";
|
||||
f;`,
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
////t/**/
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("Test1", "function Test1(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "Test1", source: "/a" }, "function Test1(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains("Test2", "import Test2", "", "alias", /*spanIndex*/ undefined, /*hasAction*/ undefined);
|
||||
verify.not.completionListContains({ name: "Test2", source: "/a" });
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "Test1",
|
||||
source: "/a",
|
||||
description: `Add 'Test1' to existing import declaration from "./a".`,
|
||||
newFileContent: `import { Test2, Test1 } from "./a";
|
||||
t`,
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import * as a from "./a";
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// Tests that we don't filter out a completion for an alias,
|
||||
// so long as it's not an alias to a different module.
|
||||
|
||||
// @Filename: /a.ts
|
||||
////const foo = 0;
|
||||
////export { foo };
|
||||
|
||||
// @Filename: /a_reexport.ts
|
||||
// Should not show up in completions
|
||||
////export { foo } from "./a";
|
||||
|
||||
// @Filename: /b.ts
|
||||
////fo/**/
|
||||
|
||||
goTo.marker("");
|
||||
// https://github.com/Microsoft/TypeScript/issues/14003
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "import foo", "", "alias", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.not.completionListContains({ name: "foo", source: "/a_reexport" });
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
source: "/a",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import { foo } from "./a";\r
|
||||
\r
|
||||
fo`,
|
||||
});
|
||||
@@ -8,4 +8,4 @@
|
||||
/////**/
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export const foo = 0;
|
||||
|
||||
// @Filename: /b.ts
|
||||
////const foo = 1;
|
||||
////fo/**/
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "const foo: 1", "", "const");
|
||||
verify.not.completionListContains({ name: "foo", source: "/a" });
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
/////**
|
||||
//// * @typedef {object} T
|
||||
//// * /**/
|
||||
//// */
|
||||
|
||||
goTo.marker();
|
||||
verify.completionListContains("@property", "@property", "", "keyword");
|
||||
@@ -7,7 +7,7 @@
|
||||
// @Filename: foo.js
|
||||
//// function foo() {
|
||||
//// var i = 10;
|
||||
//// /*a*/return i++;/*b*/
|
||||
//// /*a*/return i + 1;/*b*/
|
||||
//// }
|
||||
|
||||
goTo.select('a', 'b');
|
||||
@@ -18,13 +18,11 @@ edit.applyRefactor({
|
||||
newContent:
|
||||
`function foo() {
|
||||
var i = 10;
|
||||
let __return;
|
||||
({ __return, i } = /*RENAME*/newFunction(i));
|
||||
return __return;
|
||||
return /*RENAME*/newFunction(i);
|
||||
}
|
||||
|
||||
function newFunction(i) {
|
||||
return { __return: i++, i };
|
||||
return i + 1;
|
||||
}
|
||||
`
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// You may not extract variable declarations with the export modifier
|
||||
|
||||
//// namespace NS {
|
||||
//// /*start*/export var x = 10;/*end*/
|
||||
//// /*start*/export var x = 10, y = 15;/*end*/
|
||||
//// }
|
||||
|
||||
goTo.select('start', 'end')
|
||||
|
||||
@@ -142,7 +142,7 @@ declare namespace FourSlashInterface {
|
||||
constructor(negative?: boolean);
|
||||
completionListCount(expectedCount: number): void;
|
||||
completionListContains(
|
||||
symbol: string,
|
||||
entryId: string | { name: string, source?: string },
|
||||
text?: string,
|
||||
documentation?: string,
|
||||
kind?: string,
|
||||
@@ -196,6 +196,7 @@ declare namespace FourSlashInterface {
|
||||
): void; //TODO: better type
|
||||
applyCodeActionFromCompletion(markerName: string, options: {
|
||||
name: string,
|
||||
source?: string,
|
||||
description: string,
|
||||
newFileContent?: string,
|
||||
newRangeContent?: string,
|
||||
|
||||
Reference in New Issue
Block a user