Compare commits
39 commits
main
...
exportsinc
Author | SHA1 | Date | |
---|---|---|---|
1645750300 | |||
474653dfcc | |||
71c7c3f46f | |||
211e3f92bb | |||
49bb2b8d8b | |||
a41f3dfff6 | |||
f0c983a605 | |||
d1bdc25a9a | |||
bc14bb0fe4 | |||
e68c951930 | |||
72dd99fec9 | |||
b11f6e8f3e | |||
2ea36a603c | |||
8e5febb4c6 | |||
13a47e2405 | |||
fa33d5016b | |||
de7b821a82 | |||
e912a76682 | |||
e7d966b2e6 | |||
2875c15bbd | |||
8d5e075394 | |||
22c3373aee | |||
380b2994b2 | |||
95a9c01ca8 | |||
ae0ab477f7 | |||
abe1fdb0ea | |||
041302fa1d | |||
b024285c23 | |||
c5cc2f148c | |||
c838093e98 | |||
9940c9261a | |||
25831a8261 | |||
0aa865f6cc | |||
8a1a124856 | |||
15b73d09c3 | |||
a84b5b58ee | |||
087de799d2 | |||
5bef866249 | |||
d99675bb74 |
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue