Compare commits

...

39 commits

Author SHA1 Message Date
Andy Hanson 1645750300 wip 2017-09-27 12:38:55 -07:00
Andy Hanson 474653dfcc wip: tests pass 2017-09-27 11:39:20 -07:00
Andy Hanson 71c7c3f46f Merge branch 'exportsincompletionlist' of https://github.com/minestarks/TypeScript into exportsincompletionlist 2017-09-27 11:09:06 -07:00
Mine Starks 211e3f92bb Lint 2017-08-16 16:59:07 -07:00
Mine Starks 49bb2b8d8b Merge branch 'master' of https://github.com/Microsoft/TypeScript into importcompletions-rebase 2017-08-16 16:56:04 -07:00
Mine Starks a41f3dfff6 Filter symbols after gathering exports instead of before 2017-08-16 10:49:35 -07:00
Mine Starks f0c983a605 importFixes.ts: Remove dummy getCanonicalFileName line 2017-08-10 14:18:09 -07:00
Mine Starks d1bdc25a9a importFixes.ts: Use local host instead of context.host in getCodeActionForImport 2017-08-10 14:18:09 -07:00
Mine Starks bc14bb0fe4 importFixes.ts: Use local newLineCharacter instead of context.newLineCharacter in getCodeActionForImport 2017-08-10 14:16:04 -07:00
Mine Starks e68c951930 importFixes.ts: Create and use importFixContext within getCodeActions lambda 2017-08-10 14:16:04 -07:00
Mine Starks 72dd99fec9 completions.ts: In getCompletionEntryDetails, if there's symbolOriginInfo, call getCodeActionForImport 2017-08-10 14:06:33 -07:00
Mine Starks b11f6e8f3e importFixes.ts: Move getCodeActionForImport out into an export, immediately below convertToImportCodeFixContext 2017-08-10 14:06:33 -07:00
Mine Starks 2ea36a603c importFixes.ts: Remove local getUniqueSymbolId function and add checker parameter to calls to it 2017-08-10 14:04:40 -07:00
Mine Starks 8e5febb4c6 importFixes.ts: Remove useCaseSensitiveFileNames altogether from getCodeActions lambda 2017-08-10 14:03:30 -07:00
Mine Starks 13a47e2405 importFixes.ts: Use symbolToken in getCodeActionForImport 2017-08-10 14:02:06 -07:00
Mine Starks fa33d5016b Move the declaration for lastImportDeclaration out of the getCodeActions lambda into getCodeActionForImport 2017-08-10 13:59:11 -07:00
Mine Starks de7b821a82 importFixes.ts: Move createCodeAction out, immediately above convertToImportCodeFixContext 2017-08-10 13:57:28 -07:00
Mine Starks e912a76682 importFixes.ts: Use cachedImportDeclarations from context in getCodeActionForImport 2017-08-10 13:54:27 -07:00
Mine Starks e7d966b2e6 importFixes.ts: Remove moduleSymbol parameter from getImportDeclarations and use the ambient one 2017-08-10 13:52:30 -07:00
Mine Starks 2875c15bbd importFixes.ts: Add context: ImportCodeFixContext parameter to getCodeActionForImport, update call sites, destructure it, use compilerOptions in getModuleSpecifierForNewImport 2017-08-10 13:50:23 -07:00
Mine Starks 8d5e075394 importFixes.ts: Add convertToImportCodeFixContext function and reference it from the getCodeActions lambda 2017-08-10 13:43:26 -07:00
Mine Starks 22c3373aee importFixes.ts: Move createChangeTracker into getCodeActionForImport, immediately after getImportDeclarations 2017-08-10 13:41:16 -07:00
Mine Starks 380b2994b2 Move getImportDeclarations into getCodeActionForImport, immediately after the implementation 2017-08-10 13:31:28 -07:00
Mine Starks 95a9c01ca8 importFixes.ts: Add types ImportDeclarationMap and ImportCodeFixContext 2017-08-10 13:16:18 -07:00
Mine Starks ae0ab477f7 completions.ts: Add TODO comment 2017-08-10 13:16:17 -07:00
Mine Starks abe1fdb0ea completions.ts, services.ts: Plumb host and rulesProvider into getCompletionEntryDetails 2017-08-10 13:16:17 -07:00
Mine Starks 041302fa1d completions.ts: Populate list with possible exports (implement getSymbolsFromOtherSourceFileExports) 2017-08-10 13:16:17 -07:00
Mine Starks b024285c23 completions.ts: Set CompletionEntry.hasAction when symbol is found in symbolToOriginInfoMap (meaning there's an import action) 2017-08-10 13:15:12 -07:00
Mine Starks c5cc2f148c utilities.ts: Add getOtherModuleSymbols, getUniqueSymbolIdAsString, getUniqueSymbolId 2017-08-10 12:59:53 -07:00
Mine Starks c838093e98 completions.ts: add symbolToOriginInfoMap parameter to getCompletionEntriesFromSymbols and to return value of getCompletionData 2017-08-10 12:58:31 -07:00
Mine Starks 9940c9261a completions.ts, services.ts: Plumb allSourceFiles into new function getSymbolsFromOtherSourceFileExports inside getCompletionData 2017-08-10 11:12:01 -07:00
Mine Starks 25831a8261 completions.ts, services.ts: Add allSourceFiles parameter to getCompletionsAtPosition 2017-08-10 11:08:40 -07:00
Mine Starks 0aa865f6cc completions.ts: define SymbolOriginInfo type 2017-08-10 11:07:23 -07:00
Mine Starks 8a1a124856 session.ts, services.ts, types.ts: Add formattingOptions parameter to getCompletionEntryDetails 2017-08-10 11:07:22 -07:00
Mine Starks 15b73d09c3 protocol.ts, session.ts, types.ts: add hasAction to CompletionEntry 2017-08-10 11:05:52 -07:00
Mine Starks a84b5b58ee protocol.ts, session.ts: Add codeActions member to CompletionEntryDetails protocol 2017-08-10 11:03:01 -07:00
Mine Starks 087de799d2 client.ts, completions.ts, types.ts: Add codeActions member to CompletionEntryDetails 2017-08-10 10:56:20 -07:00
Mine Starks 5bef866249 tsserverProjectSystem.ts: add two tests 2017-08-10 10:56:19 -07:00
Mine Starks d99675bb74 checker.ts: Remove null check on symbols 2017-08-10 10:56:19 -07:00
11 changed files with 875 additions and 554 deletions

View file

@ -6251,11 +6251,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;
}

View file

@ -3788,6 +3788,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 = {
@ -4127,6 +4164,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 = {

View file

@ -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 {

View file

@ -1659,6 +1659,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;
}
/**
@ -1691,6 +1696,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 {

View file

@ -1186,9 +1186,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));
}
@ -1201,9 +1207,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> {

File diff suppressed because it is too large Load diff

View file

@ -4,13 +4,23 @@
namespace ts.Completions {
export type Log = (message: string) => void;
export type SymbolOriginInfo = { moduleSymbol: Symbol, isDefaultExport?: boolean };
const enum KeywordCompletionFilters {
None,
ClassElementKeywords, // Keywords at class keyword
ConstructorParameterKeywords, // Keywords at constructor parameter
}
export function getCompletionsAtPosition(host: LanguageServiceHost, typeChecker: TypeChecker, log: Log, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number): CompletionInfo | undefined {
export function getCompletionsAtPosition(
host: LanguageServiceHost,
typeChecker: TypeChecker,
log: Log,
compilerOptions: CompilerOptions,
sourceFile: SourceFile,
position: number,
allSourceFiles: ReadonlyArray<SourceFile>,
): CompletionInfo | undefined {
if (isInReferenceComment(sourceFile, position)) {
return PathCompletions.getTripleSlashReferenceCompletion(sourceFile, position, compilerOptions, host);
}
@ -19,12 +29,12 @@ namespace ts.Completions {
return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log);
}
const completionData = getCompletionData(typeChecker, log, sourceFile, position);
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
if (!completionData) {
return undefined;
}
const { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, request, keywordFilters } = completionData;
const { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap } = completionData;
if (sourceFile.languageVariant === LanguageVariant.JSX &&
location && location.parent && location.parent.kind === SyntaxKind.JsxClosingElement) {
@ -56,7 +66,7 @@ namespace ts.Completions {
const entries: CompletionEntry[] = [];
if (isSourceFileJavaScript(sourceFile)) {
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral);
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, symbolToOriginInfoMap);
getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target, entries);
}
else {
@ -64,7 +74,7 @@ namespace ts.Completions {
return undefined;
}
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral);
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, symbolToOriginInfoMap);
}
// TODO add filter for keyword based on type/value/namespace and also location
@ -134,7 +144,17 @@ namespace ts.Completions {
};
}
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: Push<CompletionEntry>, location: Node, performCharacterChecks: boolean, typeChecker: TypeChecker, target: ScriptTarget, log: Log, allowStringLiteral: boolean): Map<true> {
function getCompletionEntriesFromSymbols(
symbols: Symbol[],
entries: Push<CompletionEntry>,
location: Node,
performCharacterChecks: boolean,
typeChecker: TypeChecker,
target: ScriptTarget,
log: Log,
allowStringLiteral: boolean,
symbolToOriginInfoMap?: Map<SymbolOriginInfo>,
): Map<true> {
const start = timestamp();
const uniqueNames = createMap<true>();
if (symbols) {
@ -143,6 +163,9 @@ namespace ts.Completions {
if (entry) {
const id = entry.name;
if (!uniqueNames.has(id)) {
if (symbolToOriginInfoMap && symbolToOriginInfoMap.has(getUniqueSymbolIdAsString(symbol, typeChecker))) {
entry.hasAction = true;
}
entries.push(entry);
uniqueNames.set(id, true);
}
@ -298,11 +321,22 @@ namespace ts.Completions {
}
}
export function getCompletionEntryDetails(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): CompletionEntryDetails {
export function getCompletionEntryDetails(
typeChecker: TypeChecker,
log: (message: string) => void,
compilerOptions: CompilerOptions,
sourceFile: SourceFile,
position: number,
entryName: string,
allSourceFiles: ReadonlyArray<SourceFile>,
host?: LanguageServiceHost,
rulesProvider?: formatting.RulesProvider,
): CompletionEntryDetails {
// Compute all the completion symbols again.
const completionData = getCompletionData(typeChecker, log, sourceFile, position);
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
if (completionData) {
const { symbols, location, allowStringLiteral } = 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
@ -311,6 +345,26 @@ namespace ts.Completions {
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === entryName ? s : undefined);
if (symbol) {
let codeActions: CodeAction[];
if (host && rulesProvider) {
const symbolOriginInfo = symbolToOriginInfoMap.get(getUniqueSymbolIdAsString(symbol, typeChecker));
if (symbolOriginInfo) {
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false;
const context: codefix.ImportCodeFixContext = {
host,
checker: typeChecker,
newLineCharacter: host.getNewLine(),
compilerOptions,
sourceFile,
rulesProvider,
symbolName: symbol.name,
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames)
};
codeActions = codefix.getCodeActionForImport(/*moduleSymbol*/ symbolOriginInfo.moduleSymbol, context, context.symbolName, /*isDefault*/ symbolOriginInfo.isDefaultExport);
}
}
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
return {
name: entryName,
@ -318,7 +372,8 @@ namespace ts.Completions {
kind: symbolKind,
displayParts,
documentation,
tags
tags,
codeActions
};
}
}
@ -335,16 +390,25 @@ namespace ts.Completions {
kindModifiers: ScriptElementKindModifier.none,
displayParts: [displayPart(entryName, SymbolDisplayPartKind.keyword)],
documentation: undefined,
tags: undefined
tags: undefined,
codeActions: undefined
};
}
return undefined;
}
export function getCompletionEntrySymbol(typeChecker: TypeChecker, log: (message: string) => void, compilerOptions: CompilerOptions, sourceFile: SourceFile, position: number, entryName: string): Symbol | undefined {
export function getCompletionEntrySymbol(
typeChecker: TypeChecker,
log: (message: string) => void,
compilerOptions: CompilerOptions,
sourceFile: SourceFile,
position: number,
entryName: string,
allSourceFiles: ReadonlyArray<SourceFile>,
): Symbol | undefined {
// Compute all the completion symbols again.
const completionData = getCompletionData(typeChecker, log, sourceFile, position);
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles);
if (!completionData) {
return undefined;
}
@ -366,10 +430,17 @@ namespace ts.Completions {
isRightOfDot: boolean;
request?: Request;
keywordFilters: KeywordCompletionFilters;
symbolToOriginInfoMap: Map<SymbolOriginInfo>;
}
type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag };
function getCompletionData(typeChecker: TypeChecker, log: (message: string) => void, sourceFile: SourceFile, position: number): CompletionData | undefined {
function getCompletionData(
typeChecker: TypeChecker,
log: (message: string) => void,
sourceFile: SourceFile,
position: number,
allSourceFiles: ReadonlyArray<SourceFile>,
): CompletionData | undefined {
const isJavaScriptFile = isSourceFileJavaScript(sourceFile);
let request: Request | undefined;
@ -441,7 +512,18 @@ namespace ts.Completions {
}
if (request) {
return { symbols: undefined, isGlobalCompletion: false, isMemberCompletion: false, allowStringLiteral: false, isNewIdentifierLocation: false, location: undefined, isRightOfDot: false, request, keywordFilters: KeywordCompletionFilters.None };
return {
symbols: undefined,
isGlobalCompletion: false,
isMemberCompletion: false,
allowStringLiteral: false,
isNewIdentifierLocation: false,
location: undefined,
isRightOfDot: false,
request,
keywordFilters: KeywordCompletionFilters.None,
symbolToOriginInfoMap: undefined,
};
}
if (!insideJsDocTagTypeExpression) {
@ -523,7 +605,6 @@ namespace ts.Completions {
break;
}
// falls through
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxOpeningElement:
@ -543,6 +624,7 @@ namespace ts.Completions {
let isNewIdentifierLocation: boolean;
let keywordFilters = KeywordCompletionFilters.None;
let symbols: Symbol[] = [];
const symbolToOriginInfoMap = createMap<SymbolOriginInfo>();
if (isRightOfDot) {
getTypeScriptMemberSymbols();
@ -579,7 +661,7 @@ namespace ts.Completions {
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters };
return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters, symbolToOriginInfoMap };
type JSDocTagWithTypeExpression = JSDocAugmentsTag | JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
@ -753,7 +835,10 @@ namespace ts.Completions {
}
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
symbols = filterGlobalCompletion(typeChecker.getSymbolsInScope(scopeNode, symbolMeanings));
symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);
symbols.push(...getSymbolsFromOtherSourceFileExports(symbols, previousToken === undefined ? "" : previousToken.getText()));
symbols = filterGlobalCompletion(symbols);
return true;
}
@ -833,6 +918,36 @@ namespace ts.Completions {
}
}
function getSymbolsFromOtherSourceFileExports(knownSymbols: Symbol[], tokenText: string): Symbol[] {
const otherSourceFileExports: Symbol[] = [];
const tokenTextLowerCase = tokenText.toLowerCase();
const symbolIdMap = arrayToMap(knownSymbols, s => getUniqueSymbolIdAsString(s, typeChecker));
eachOtherModuleSymbol(allSourceFiles, sourceFile, typeChecker, moduleSymbol => {
// check the default export
const defaultExport = typeChecker.tryGetMemberInModuleExports("default", moduleSymbol);
if (defaultExport) {
const localSymbol = getLocalSymbolForExportDefault(defaultExport);
if (localSymbol && !symbolIdMap.has(getUniqueSymbolIdAsString(localSymbol, typeChecker)) && startsWith(localSymbol.name.toLowerCase(), tokenTextLowerCase)) {
otherSourceFileExports.push(localSymbol);
symbolToOriginInfoMap.set(getUniqueSymbolIdAsString(localSymbol, typeChecker), { moduleSymbol, isDefaultExport: true });
}
}
// check exports with the same name
const allExportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
if (allExportedSymbols) {
for (const exportedSymbol of allExportedSymbols) {
if (exportedSymbol.name && !symbolIdMap.has(getUniqueSymbolIdAsString(exportedSymbol, typeChecker)) && startsWith(exportedSymbol.name.toLowerCase(), tokenTextLowerCase)) {
otherSourceFileExports.push(exportedSymbol);
symbolToOriginInfoMap.set(getUniqueSymbolIdAsString(exportedSymbol, typeChecker), { moduleSymbol });
}
}
}
});
return otherSourceFileExports;
}
/**
* Finds the first node that "embraces" the position, so that one may
* accurately aggregate locals from the closest containing scope.
@ -1782,4 +1897,21 @@ namespace ts.Completions {
// If there are no property-only types, just provide completions for every type as usual.
return checker.getAllPossiblePropertiesOfTypes(filteredTypes);
}
function eachOtherModuleSymbol(
sourceFiles: ReadonlyArray<SourceFile>,
currentSourceFile: SourceFile,
typeChecker: TypeChecker,
cb: (symbol: Symbol) => void,
) {
for (const a of typeChecker.getAmbientModules()) {
cb(a);
}
for (const otherSourceFile of sourceFiles) {
if (otherSourceFile !== currentSourceFile && isExternalOrCommonJsModule(otherSourceFile)) {
cb(otherSourceFile.symbol);
}
}
}
}

View file

@ -1377,17 +1377,19 @@ namespace ts {
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
synchronizeHostData();
return Completions.getCompletionsAtPosition(host, program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position);
return Completions.getCompletionsAtPosition(host, program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, program.getSourceFiles());
}
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
function getCompletionEntryDetails(fileName: string, position: number, entryName: string, formattingOptions?: FormatCodeSettings): CompletionEntryDetails {
synchronizeHostData();
return Completions.getCompletionEntryDetails(program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, entryName);
const ruleProvider = formattingOptions ? getRuleProvider(formattingOptions) : undefined;
return Completions.getCompletionEntryDetails(
program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, entryName, program.getSourceFiles(), host, ruleProvider);
}
function getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol {
synchronizeHostData();
return Completions.getCompletionEntrySymbol(program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, entryName);
return Completions.getCompletionEntrySymbol(program.getTypeChecker(), log, program.getCompilerOptions(), getValidSourceFile(fileName), position, entryName, program.getSourceFiles());
}
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {

View file

@ -190,7 +190,8 @@ namespace ts.textChanges {
private changes: Change[] = [];
private readonly newLineCharacter: string;
public static fromContext(context: RefactorContext | CodeFixContext) {
//todo: don't include ImportCodeFixContext
public static fromContext(context: RefactorContext | CodeFixContext | ts.codefix.ImportCodeFixContext) {
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider);
}

View file

@ -228,7 +228,8 @@ namespace ts {
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
//We should probably not take formatting options here.
getCompletionEntryDetails(fileName: string, position: number, entryName: string, formattingOptions?: FormatCodeSettings): CompletionEntryDetails;
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
@ -665,6 +666,7 @@ namespace ts {
* be used in that case
*/
replacementSpan?: TextSpan;
hasAction?: true; //why do we need this?
}
export interface CompletionEntryDetails {
@ -674,6 +676,7 @@ namespace ts {
displayParts: SymbolDisplayPart[];
documentation: SymbolDisplayPart[];
tags: JSDocTagInfo[];
codeActions?: CodeAction[];
}
export interface OutliningSpan {

View file

@ -1313,10 +1313,21 @@ namespace ts {
export function getScriptKind(fileName: string, host?: LanguageServiceHost): ScriptKind {
// First check to see if the script kind was specified by the host. Chances are the host
// may override the default script kind for the file extension.
// may override the default script kind for the file extensison.
return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName));
}
export function getUniqueSymbolIdAsString(symbol: Symbol, typeChecker: TypeChecker) {
return getUniqueSymbolId(symbol, typeChecker) + "";
}
export function getUniqueSymbolId(symbol: Symbol, typeChecker: TypeChecker) {
if (symbol.flags & SymbolFlags.Alias) {
return getSymbolId(typeChecker.getAliasedSymbol(symbol));
}
return getSymbolId(symbol);
}
export function getFirstNonSpaceCharacterPosition(text: string, position: number) {
while (isWhiteSpaceLike(text.charCodeAt(position))) {
position += 1;