Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exported members of all project files in the global completion list #17851

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d99675b
checker.ts: Remove null check on symbols
minestarks Aug 10, 2017
5bef866
tsserverProjectSystem.ts: add two tests
minestarks Aug 10, 2017
087de79
client.ts, completions.ts, types.ts: Add codeActions member to Comple…
minestarks Aug 10, 2017
a84b5b5
protocol.ts, session.ts: Add codeActions member to CompletionEntryDet…
minestarks Aug 10, 2017
15b73d0
protocol.ts, session.ts, types.ts: add hasAction to CompletionEntry
minestarks Aug 10, 2017
8a1a124
session.ts, services.ts, types.ts: Add formattingOptions parameter to…
minestarks Aug 10, 2017
0aa865f
completions.ts: define SymbolOriginInfo type
minestarks Aug 10, 2017
25831a8
completions.ts, services.ts: Add allSourceFiles parameter to getCompl…
minestarks Aug 10, 2017
9940c92
completions.ts, services.ts: Plumb allSourceFiles into new function g…
minestarks Aug 10, 2017
c838093
completions.ts: add symbolToOriginInfoMap parameter to getCompletionE…
minestarks Aug 10, 2017
c5cc2f1
utilities.ts: Add getOtherModuleSymbols, getUniqueSymbolIdAsString, g…
minestarks Aug 10, 2017
b024285
completions.ts: Set CompletionEntry.hasAction when symbol is found in…
minestarks Aug 10, 2017
041302f
completions.ts: Populate list with possible exports (implement getSym…
minestarks Aug 10, 2017
abe1fdb
completions.ts, services.ts: Plumb host and rulesProvider into getCom…
minestarks Aug 10, 2017
ae0ab47
completions.ts: Add TODO comment
minestarks Aug 10, 2017
95a9c01
importFixes.ts: Add types ImportDeclarationMap and ImportCodeFixContext
minestarks Aug 10, 2017
380b299
Move getImportDeclarations into getCodeActionForImport, immediately a…
minestarks Aug 10, 2017
22c3373
importFixes.ts: Move createChangeTracker into getCodeActionForImport,…
minestarks Aug 10, 2017
8d5e075
importFixes.ts: Add convertToImportCodeFixContext function and refere…
minestarks Aug 10, 2017
2875c15
importFixes.ts: Add context: ImportCodeFixContext parameter to getCod…
minestarks Aug 10, 2017
e7d966b
importFixes.ts: Remove moduleSymbol parameter from getImportDeclarati…
minestarks Aug 10, 2017
e912a76
importFixes.ts: Use cachedImportDeclarations from context in getCodeA…
minestarks Aug 10, 2017
de7b821
importFixes.ts: Move createCodeAction out, immediately above convertT…
minestarks Aug 10, 2017
fa33d50
Move the declaration for lastImportDeclaration out of the getCodeActi…
minestarks Aug 10, 2017
13a47e2
importFixes.ts: Use symbolToken in getCodeActionForImport
minestarks Aug 10, 2017
8e5febb
importFixes.ts: Remove useCaseSensitiveFileNames altogether from getC…
minestarks Aug 10, 2017
2ea36a6
importFixes.ts: Remove local getUniqueSymbolId function and add check…
minestarks Aug 10, 2017
b11f6e8
importFixes.ts: Move getCodeActionForImport out into an export, immed…
minestarks Aug 10, 2017
72dd99f
completions.ts: In getCompletionEntryDetails, if there's symbolOrigin…
minestarks Aug 10, 2017
e68c951
importFixes.ts: Create and use importFixContext within getCodeActions…
minestarks Aug 10, 2017
bc14bb0
importFixes.ts: Use local newLineCharacter instead of context.newLine…
minestarks Aug 10, 2017
d1bdc25
importFixes.ts: Use local host instead of context.host in getCodeActi…
minestarks Aug 10, 2017
f0c983a
importFixes.ts: Remove dummy getCanonicalFileName line
minestarks Aug 10, 2017
a41f3df
Filter symbols after gathering exports instead of before
minestarks Aug 16, 2017
49bb2b8
Merge branch 'master' of https://github.com/Microsoft/TypeScript into…
minestarks Aug 16, 2017
211e3f9
Lint
minestarks Aug 16, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6233,11 +6233,13 @@ namespace ts {

function symbolsToArray(symbols: SymbolTable): Symbol[] {
const result: Symbol[] = [];
symbols.forEach((symbol, id) => {
if (!isReservedMemberName(id)) {
result.push(symbol);
}
});
if (symbols) {
symbols.forEach((symbol, id) => {
if (!isReservedMemberName(id)) {
result.push(symbol);
}
});
}
return result;
}

Expand Down
101 changes: 101 additions & 0 deletions src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3751,6 +3751,43 @@ namespace ts.projectSystem {
});
});

describe("import in completion list", () => {
it("should include exported members of all source files", () => {
const file1: FileOrFolder = {
path: "/a/b/file1.ts",
content: `
export function Test1() { }
export function Test2() { }
`
};
const file2: FileOrFolder = {
path: "/a/b/file2.ts",
content: `
import { Test2 } from "./file1";

t`
};
const configFile: FileOrFolder = {
path: "/a/b/tsconfig.json",
content: "{}"
};

const host = createServerHost([file1, file2, configFile]);
const service = createProjectService(host);
service.openClientFile(file2.path);

const completions1 = service.configuredProjects[0].getLanguageService().getCompletionsAtPosition(file2.path, file2.content.length);
const test1Entry = find(completions1.entries, e => e.name === "Test1");
const test2Entry = find(completions1.entries, e => e.name === "Test2");

assert.isDefined(test1Entry, "should contain 'Test1'");
assert.isDefined(test2Entry, "should contain 'Test2'");

assert.isTrue(test1Entry.hasAction, "should set the 'hasAction' property to true for Test1");
assert.isUndefined(test2Entry.hasAction, "should not set the 'hasAction' property for Test2");
});
});

describe("import helpers", () => {
it("should not crash in tsserver", () => {
const f1 = {
Expand Down Expand Up @@ -4090,6 +4127,70 @@ namespace ts.projectSystem {
});
});

describe("completion entry with code actions", () => {
it("should work for symbols from non-imported modules", () => {
const moduleFile = {
path: "/a/b/moduleFile.ts",
content: `export const guitar = 10;`
};
const file1 = {
path: "/a/b/file2.ts",
content: `var x:`
};
const globalFile = {
path: "/a/b/globalFile.ts",
content: `interface Jazz { }`
};
const ambientModuleFile = {
path: "/a/b/ambientModuleFile.ts",
content:
`declare module "windyAndWarm" {
export const chetAtkins = "great";
}`
};
const defaultModuleFile = {
path: "/a/b/defaultModuleFile.ts",
content:
`export default function egyptianElla() { };`
};
const configFile = {
path: "/a/b/tsconfig.json",
content: "{}"
};

const host = createServerHost([moduleFile, file1, globalFile, ambientModuleFile, defaultModuleFile, configFile]);
const session = createSession(host);
const projectService = session.getProjectService();
projectService.openClientFile(file1.path);

checkEntryDetail(1, "guitar", /*hasAction*/ true, `import { guitar } from "./moduleFile";\n\n`);
checkEntryDetail(1, "chetAtkins", /*hasAction*/ true, `import { chetAtkins } from "windyAndWarm";\n\n`);
checkEntryDetail(1, "egyptianElla", /*hasAction*/ true, `import egyptianElla from "./defaultModuleFile";\n\n`);
checkEntryDetail(7, "Jazz", /*hasAction*/ false);

function checkEntryDetail(offset: number, entryName: string, hasAction: boolean, insertString?: string) {
const request = makeSessionRequest<protocol.CompletionDetailsRequestArgs>(
CommandNames.CompletionDetails,
{ entryNames: [entryName], file: file1.path, line: 1, offset, projectFileName: configFile.path });
const response = session.executeCommand(request).response as protocol.CompletionEntryDetails[];
assert.equal(response.length, 1);

const entryDetails = response[0];
if (!hasAction) {
assert.isUndefined(entryDetails.codeActions);
}
else {
const action = entryDetails.codeActions[0];
assert.equal(action.changes[0].fileName, file1.path);
assert.deepEqual(action.changes[0], <protocol.FileCodeEdits>{
fileName: file1.path,
textChanges: [{ start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 }, newText: insertString }]
});
}
}
});
});

describe("maxNodeModuleJsDepth for inferred projects", () => {
it("should be set to 2 if the project has js root files", () => {
const file1: FileOrFolder = {
Expand Down
4 changes: 3 additions & 1 deletion src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ namespace ts.server {
const request = this.processRequest<protocol.CompletionDetailsRequest>(CommandNames.CompletionDetails, args);
const response = this.processResponse<protocol.CompletionDetailsResponse>(request);
Debug.assert(response.body.length === 1, "Unexpected length of completion details response body.");
return response.body[0];

const convertedCodeActions = map(response.body[0].codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
return { ...response.body[0], codeActions: convertedCodeActions };
}

getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol {
Expand Down
10 changes: 10 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,11 @@ namespace ts.server.protocol {
* this span should be used instead of the default one.
*/
replacementSpan?: TextSpan;
/**
* Indicating if commiting this completion entry will require additional code action to be
* made to avoid errors. The code action is normally adding an additional import statement.
*/
hasAction?: true;
}

/**
Expand Down Expand Up @@ -1674,6 +1679,11 @@ namespace ts.server.protocol {
* JSDoc tags for the symbol.
*/
tags: JSDocTagInfo[];

/**
* The associated code actions for this entry
*/
codeActions?: CodeAction[];
}

export interface CompletionsResponse extends Response {
Expand Down
23 changes: 19 additions & 4 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1178,9 +1178,15 @@ namespace ts.server {
if (simplifiedResult) {
return mapDefined(completions && completions.entries, entry => {
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
const { name, kind, kindModifiers, sortText, replacementSpan, hasAction } = entry;
const convertedSpan = replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };

const newEntry: protocol.CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };
// avoid serialization when hasAction = false
if (hasAction) {
newEntry.hasAction = true;
}
return newEntry;
}
}).sort((a, b) => compareStrings(a.name, b.name));
}
Expand All @@ -1193,9 +1199,18 @@ namespace ts.server {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const formattingOptions = project.projectService.getFormatCodeOptions(file);

return mapDefined(args.entryNames, entryName =>
project.getLanguageService().getCompletionEntryDetails(file, position, entryName));
return mapDefined(args.entryNames, entryName => {
const details = project.getLanguageService().getCompletionEntryDetails(file, position, entryName, formattingOptions);
if (details) {
const mappedCodeActions = map(details.codeActions, action => this.mapCodeAction(action, scriptInfo));
return { ...details, codeActions: mappedCodeActions };
}
else {
return undefined;
}
});
}

private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): ReadonlyArray<protocol.CompileOnSaveAffectedFileListSingleProject> {
Expand Down
Loading