Compare commits
39 commits
main
...
exportsinc
Author | SHA1 | Date | |
---|---|---|---|
30ebde4112 | |||
64e30dbc25 | |||
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 |
|
@ -296,6 +296,7 @@ namespace ts {
|
|||
const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
|
||||
|
||||
const globals = createSymbolTable();
|
||||
let ambientModulesCache: Symbol[] | undefined;
|
||||
/**
|
||||
* List of every ambient module with a "*" wildcard.
|
||||
* Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches.
|
||||
|
@ -25521,13 +25522,16 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getAmbientModules(): Symbol[] {
|
||||
const result: Symbol[] = [];
|
||||
globals.forEach((global, sym) => {
|
||||
if (ambientModuleSymbolRegex.test(unescapeLeadingUnderscores(sym))) {
|
||||
result.push(global);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
if (!ambientModulesCache) {
|
||||
ambientModulesCache = [];
|
||||
globals.forEach((global, sym) => {
|
||||
// No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module.
|
||||
if (ambientModuleSymbolRegex.test(sym as string)) {
|
||||
ambientModulesCache.push(global);
|
||||
}
|
||||
});
|
||||
}
|
||||
return ambientModulesCache;
|
||||
}
|
||||
|
||||
function checkGrammarImportCallExpression(node: ImportCall): boolean {
|
||||
|
|
|
@ -1183,8 +1183,8 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function isDoubleQuotedString(node: Node) {
|
||||
return node.kind === SyntaxKind.StringLiteral && getSourceTextOfNodeFromSourceFile(sourceFile, node).charCodeAt(0) === CharacterCodes.doubleQuote;
|
||||
function isDoubleQuotedString(node: Node): boolean {
|
||||
return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -191,6 +191,18 @@ namespace ts {
|
|||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */
|
||||
export function firstDefined<T, U>(array: ReadonlyArray<T> | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const result = callback(array[i], i);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the parent chain of a node and performs the callback on each parent until the callback
|
||||
* returns a truthy value, then returns that value.
|
||||
|
@ -259,6 +271,16 @@ namespace ts {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export function findLast<T>(array: ReadonlyArray<T>, predicate: (element: T, index: number) => boolean): T | undefined {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
const value = array[i];
|
||||
if (predicate(value, i)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */
|
||||
export function findIndex<T>(array: ReadonlyArray<T>, predicate: (element: T, index: number) => boolean): number {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
|
@ -1160,6 +1182,14 @@ namespace ts {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function arrayToNumericMap<T>(array: ReadonlyArray<T>, makeKey: (value: T) => number): T[] {
|
||||
const result: T[] = [];
|
||||
for (const value of array) {
|
||||
result[makeKey(value)] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a set from the elements of an array.
|
||||
*
|
||||
|
|
|
@ -3657,7 +3657,7 @@
|
|||
"category": "Error",
|
||||
"code": 90010
|
||||
},
|
||||
"Import {0} from {1}.": {
|
||||
"Import '{0}' from \"{1}\".": {
|
||||
"category": "Message",
|
||||
"code": 90013
|
||||
},
|
||||
|
@ -3665,7 +3665,7 @@
|
|||
"category": "Message",
|
||||
"code": 90014
|
||||
},
|
||||
"Add {0} to existing import declaration from {1}.": {
|
||||
"Add '{0}' to existing import declaration from \"{1}\".": {
|
||||
"category": "Message",
|
||||
"code": 90015
|
||||
},
|
||||
|
|
|
@ -124,7 +124,11 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined {
|
||||
export interface GetEffectiveTypeRootsHost {
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
getCurrentDirectory?(): string;
|
||||
}
|
||||
export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined {
|
||||
if (options.typeRoots) {
|
||||
return options.typeRoots;
|
||||
}
|
||||
|
|
|
@ -1055,6 +1055,7 @@ namespace ts {
|
|||
export interface StringLiteral extends LiteralExpression {
|
||||
kind: SyntaxKind.StringLiteral;
|
||||
/* @internal */ textSourceNode?: Identifier | StringLiteral | NumericLiteral; // Allows a StringLiteral to get its text from another node (used by transforms).
|
||||
/** Note: this is only set when synthesizing a node, not during parsing. */
|
||||
/* @internal */ singleQuote?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -520,6 +520,17 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isAnyImportSyntax(node: Node): node is AnyImportSyntax {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the nearest enclosing block scope container that has the provided node
|
||||
// as a descendant, that is not the provided node.
|
||||
export function getEnclosingBlockScopeContainer(node: Node): Node {
|
||||
|
@ -1375,6 +1386,10 @@ namespace ts {
|
|||
return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote;
|
||||
}
|
||||
|
||||
export function isStringDoubleQuoted(string: StringLiteral, sourceFile: SourceFile): boolean {
|
||||
return getSourceTextOfNodeFromSourceFile(sourceFile, string).charCodeAt(0) === CharacterCodes.doubleQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the node is a variable declaration whose initializer is a function expression.
|
||||
* This function does not test if the node is in a JavaScript file or not.
|
||||
|
|
|
@ -783,10 +783,10 @@ namespace FourSlash {
|
|||
});
|
||||
}
|
||||
|
||||
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
|
||||
public verifyCompletionListContains(symbol: string, 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);
|
||||
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex, hasAction);
|
||||
}
|
||||
else {
|
||||
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
|
||||
|
@ -1127,7 +1127,7 @@ Actual: ${stringify(fullActual)}`);
|
|||
}
|
||||
|
||||
private getCompletionEntryDetails(entryName: string) {
|
||||
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName);
|
||||
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings);
|
||||
}
|
||||
|
||||
private getReferencesAtCaret() {
|
||||
|
@ -2289,6 +2289,29 @@ Actual: ${stringify(fullActual)}`);
|
|||
this.applyCodeActions(this.getCodeFixActions(fileName, errorCode), index);
|
||||
}
|
||||
|
||||
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
|
||||
this.goToMarker(markerName);
|
||||
|
||||
const actualCompletion = this.getCompletionListAtCaret().entries.find(e => e.name === options.name);
|
||||
|
||||
if (!actualCompletion.hasAction) {
|
||||
this.raiseError(`Completion for ${options.name} does not have an associated action.`);
|
||||
}
|
||||
|
||||
const details = this.getCompletionEntryDetails(options.name);
|
||||
if (details.codeActions.length !== 1) {
|
||||
this.raiseError(`Expected one code action, got ${details.codeActions.length}`);
|
||||
}
|
||||
|
||||
if (details.codeActions[0].description !== options.description) {
|
||||
this.raiseError(`Expected description to be:\n${options.description}\ngot:\n${details.codeActions[0].description}`);
|
||||
}
|
||||
|
||||
this.applyCodeActions(details.codeActions);
|
||||
|
||||
this.verifyNewContent(options);
|
||||
}
|
||||
|
||||
public verifyRangeIs(expectedText: string, includeWhiteSpace?: boolean) {
|
||||
const ranges = this.getRanges();
|
||||
if (ranges.length !== 1) {
|
||||
|
@ -2360,6 +2383,10 @@ Actual: ${stringify(fullActual)}`);
|
|||
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
|
||||
}
|
||||
|
||||
this.verifyNewContent(options);
|
||||
}
|
||||
|
||||
private verifyNewContent(options: FourSlashInterface.NewContentOptions) {
|
||||
if (options.newFileContent) {
|
||||
assert(!options.newRangeContent);
|
||||
this.verifyCurrentFileContent(options.newFileContent);
|
||||
|
@ -2933,7 +2960,15 @@ Actual: ${stringify(fullActual)}`);
|
|||
return text.substring(startPos, endPos);
|
||||
}
|
||||
|
||||
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
|
||||
private assertItemInCompletionList(
|
||||
items: ts.CompletionEntry[],
|
||||
name: string,
|
||||
text: string | undefined,
|
||||
documentation: string | undefined,
|
||||
kind: string | undefined,
|
||||
spanIndex: number | undefined,
|
||||
hasAction: boolean | undefined,
|
||||
) {
|
||||
for (const item of items) {
|
||||
if (item.name === name) {
|
||||
if (documentation !== undefined || text !== undefined) {
|
||||
|
@ -2956,6 +2991,8 @@ Actual: ${stringify(fullActual)}`);
|
|||
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name));
|
||||
}
|
||||
|
||||
assert.equal(item.hasAction, hasAction);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -3669,12 +3706,12 @@ 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) {
|
||||
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean) {
|
||||
if (this.negative) {
|
||||
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex);
|
||||
}
|
||||
else {
|
||||
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex);
|
||||
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex, hasAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3999,6 +4036,10 @@ namespace FourSlashInterface {
|
|||
this.state.getAndApplyCodeActions(errorCode, index);
|
||||
}
|
||||
|
||||
public applyCodeActionFromCompletion(markerName: string, options: VerifyCompletionActionOptions): void {
|
||||
this.state.applyCodeActionFromCompletion(markerName, options);
|
||||
}
|
||||
|
||||
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
|
||||
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
|
||||
}
|
||||
|
@ -4396,12 +4437,20 @@ namespace FourSlashInterface {
|
|||
isNewIdentifierLocation?: boolean;
|
||||
}
|
||||
|
||||
export interface VerifyCodeFixOptions {
|
||||
description: string;
|
||||
// One of these should be defined.
|
||||
export interface NewContentOptions {
|
||||
// Exactly one of these should be defined.
|
||||
newFileContent?: string;
|
||||
newRangeContent?: string;
|
||||
}
|
||||
|
||||
export interface VerifyCodeFixOptions extends NewContentOptions {
|
||||
description: string;
|
||||
errorCode?: number;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface VerifyCompletionActionOptions extends NewContentOptions {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -405,8 +405,8 @@ namespace Harness.LanguageService {
|
|||
getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo {
|
||||
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position));
|
||||
}
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
|
||||
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: ts.FormatCodeOptions): ts.CompletionEntryDetails {
|
||||
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(options)));
|
||||
}
|
||||
getCompletionEntrySymbol(): ts.Symbol {
|
||||
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1658,6 +1658,11 @@ namespace ts.server.protocol {
|
|||
* this span should be used instead of the default one.
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
/**
|
||||
* Indicates whether 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1690,6 +1695,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));
|
||||
}
|
||||
|
@ -1199,10 +1205,20 @@ namespace ts.server {
|
|||
|
||||
private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): ReadonlyArray<protocol.CompletionEntryDetails> {
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const position = this.getPositionInFile(args, file);
|
||||
const scriptInfo = this.projectService.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> {
|
||||
|
|
|
@ -5,15 +5,13 @@ namespace ts {
|
|||
getCodeActions(context: CodeFixContext): CodeAction[] | undefined;
|
||||
}
|
||||
|
||||
export interface CodeFixContext {
|
||||
export interface CodeFixContext extends textChanges.TextChangesContext {
|
||||
errorCode: number;
|
||||
sourceFile: SourceFile;
|
||||
span: TextSpan;
|
||||
program: Program;
|
||||
newLineCharacter: string;
|
||||
host: LanguageServiceHost;
|
||||
cancellationToken: CancellationToken;
|
||||
rulesProvider: formatting.RulesProvider;
|
||||
}
|
||||
|
||||
export namespace codefix {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,13 +4,31 @@
|
|||
namespace ts.Completions {
|
||||
export type Log = (message: string) => void;
|
||||
|
||||
interface SymbolOriginInfo {
|
||||
moduleSymbol: Symbol;
|
||||
isDefaultExport: boolean;
|
||||
}
|
||||
/**
|
||||
* Map from symbol id -> SymbolOriginInfo.
|
||||
* Only populated for symbols that come from other modules.
|
||||
*/
|
||||
type SymbolOriginInfoMap = SymbolOriginInfo[];
|
||||
|
||||
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 +37,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 +74,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 +82,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 +152,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: ReadonlyArray<Symbol>,
|
||||
entries: Push<CompletionEntry>,
|
||||
location: Node,
|
||||
performCharacterChecks: boolean,
|
||||
typeChecker: TypeChecker,
|
||||
target: ScriptTarget,
|
||||
log: Log,
|
||||
allowStringLiteral: boolean,
|
||||
symbolToOriginInfoMap?: SymbolOriginInfoMap,
|
||||
): Map<true> {
|
||||
const start = timestamp();
|
||||
const uniqueNames = createMap<true>();
|
||||
if (symbols) {
|
||||
|
@ -143,6 +171,9 @@ namespace ts.Completions {
|
|||
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);
|
||||
}
|
||||
|
@ -298,53 +329,89 @@ 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,
|
||||
name: 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
|
||||
// name against 'entryName' (which is known to be good), not building a new
|
||||
// completion entry.
|
||||
const symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === entryName ? s : undefined);
|
||||
const symbol = find(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === name);
|
||||
|
||||
if (symbol) {
|
||||
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: entryName,
|
||||
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
|
||||
kind: symbolKind,
|
||||
displayParts,
|
||||
documentation,
|
||||
tags
|
||||
};
|
||||
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions };
|
||||
}
|
||||
}
|
||||
|
||||
// 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 === entryName
|
||||
c => c.name === name
|
||||
);
|
||||
if (keywordCompletion) {
|
||||
return {
|
||||
name: entryName,
|
||||
name,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
displayParts: [displayPart(entryName, SymbolDisplayPartKind.keyword)],
|
||||
displayParts: [displayPart(name, 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 {
|
||||
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)];
|
||||
if (!symbolOriginInfo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { moduleSymbol, isDefaultExport } = symbolOriginInfo;
|
||||
return codefix.getCodeActionForImport(moduleSymbol, {
|
||||
host,
|
||||
checker,
|
||||
newLineCharacter: host.getNewLine(),
|
||||
compilerOptions,
|
||||
sourceFile,
|
||||
rulesProvider,
|
||||
symbolName: symbol.name,
|
||||
getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false),
|
||||
symbolToken: undefined,
|
||||
kind: isDefaultExport ? codefix.ImportKind.Default : codefix.ImportKind.Named,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -353,7 +420,7 @@ namespace ts.Completions {
|
|||
// 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 forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === entryName ? s : undefined);
|
||||
return find(symbols, s => getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral) === entryName);
|
||||
}
|
||||
|
||||
interface CompletionData {
|
||||
|
@ -366,10 +433,17 @@ namespace ts.Completions {
|
|||
isRightOfDot: boolean;
|
||||
request?: Request;
|
||||
keywordFilters: KeywordCompletionFilters;
|
||||
symbolToOriginInfoMap: SymbolOriginInfoMap;
|
||||
}
|
||||
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 +515,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) {
|
||||
|
@ -543,6 +628,7 @@ namespace ts.Completions {
|
|||
let isNewIdentifierLocation: boolean;
|
||||
let keywordFilters = KeywordCompletionFilters.None;
|
||||
let symbols: Symbol[] = [];
|
||||
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
|
||||
|
||||
if (isRightOfDot) {
|
||||
getTypeScriptMemberSymbols();
|
||||
|
@ -579,7 +665,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 = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
|
||||
|
||||
|
@ -752,13 +838,16 @@ namespace ts.Completions {
|
|||
}
|
||||
|
||||
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
|
||||
symbols = filterGlobalCompletion(typeChecker.getSymbolsInScope(scopeNode, symbolMeanings));
|
||||
|
||||
symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings);
|
||||
getSymbolsFromOtherSourceFileExports(symbols, previousToken === undefined ? "" : previousToken.getText());
|
||||
filterGlobalCompletion(symbols);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function filterGlobalCompletion(symbols: Symbol[]) {
|
||||
return filter(symbols, symbol => {
|
||||
function filterGlobalCompletion(symbols: Symbol[]): void {
|
||||
filterMutate(symbols, symbol => {
|
||||
if (!isSourceFile(location)) {
|
||||
// export = /**/ here we want to get all meanings, so any symbol is ok
|
||||
if (isExportAssignment(location.parent)) {
|
||||
|
@ -832,6 +921,31 @@ namespace ts.Completions {
|
|||
}
|
||||
}
|
||||
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string): void {
|
||||
const tokenTextLowerCase = tokenText.toLowerCase();
|
||||
const symbolIdMap = arrayToNumericMap(symbols, s => getUniqueSymbolId(s, typeChecker));
|
||||
|
||||
codefix.eachOtherExternalModule(typeChecker, allSourceFiles, sourceFile, moduleSymbol => {
|
||||
for (let symbol of typeChecker.getExportsOfModule(moduleSymbol)) {
|
||||
let { name } = symbol;
|
||||
const isDefaultExport = name === "default";
|
||||
if (isDefaultExport) {
|
||||
const localSymbol = getLocalSymbolForExportDefault(symbol);
|
||||
if (localSymbol) {
|
||||
symbol = localSymbol;
|
||||
name = localSymbol.name;
|
||||
}
|
||||
}
|
||||
|
||||
const id = getUniqueSymbolId(symbol, typeChecker);
|
||||
if (!symbolIdMap[id] && startsWith(name.toLowerCase(), tokenTextLowerCase)) {
|
||||
symbols.push(symbol);
|
||||
symbolToOriginInfoMap[id] = { moduleSymbol, isDefaultExport };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first node that "embraces" the position, so that one may
|
||||
* accurately aggregate locals from the closest containing scope.
|
||||
|
@ -1622,7 +1736,7 @@ namespace ts.Completions {
|
|||
// First check of the displayName is not external module; if it is an external module, it is not valid entry
|
||||
if (symbol.flags & SymbolFlags.Namespace) {
|
||||
const firstCharCode = name.charCodeAt(0);
|
||||
if (firstCharCode === CharacterCodes.singleQuote || firstCharCode === CharacterCodes.doubleQuote) {
|
||||
if (isSingleOrDoubleQuote(firstCharCode)) {
|
||||
// If the symbol is external module, don't show it in the completion list
|
||||
// (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
|
||||
return undefined;
|
||||
|
|
|
@ -339,7 +339,7 @@ namespace ts.Completions.PathCompletions {
|
|||
}
|
||||
}
|
||||
else if (host.getDirectories) {
|
||||
let typeRoots: string[];
|
||||
let typeRoots: ReadonlyArray<string>;
|
||||
try {
|
||||
// Wrap in try catch because getEffectiveTypeRoots touches the filesystem
|
||||
typeRoots = getEffectiveTypeRoots(options, host);
|
||||
|
|
|
@ -14,13 +14,11 @@ namespace ts {
|
|||
getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined;
|
||||
}
|
||||
|
||||
export interface RefactorContext {
|
||||
export interface RefactorContext extends textChanges.TextChangesContext {
|
||||
file: SourceFile;
|
||||
startPosition: number;
|
||||
endPosition?: number;
|
||||
program: Program;
|
||||
newLineCharacter: string;
|
||||
rulesProvider?: formatting.RulesProvider;
|
||||
cancellationToken?: CancellationToken;
|
||||
}
|
||||
|
||||
|
|
|
@ -1324,17 +1324,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 {
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace ts {
|
|||
getEncodedSemanticClassifications(fileName: string, start: number, length: number): string;
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number): string;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): string;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/): string;
|
||||
|
||||
getQuickInfoAtPosition(fileName: string, position: number): string;
|
||||
|
||||
|
@ -601,7 +601,7 @@ namespace ts {
|
|||
this.logger = this.host;
|
||||
}
|
||||
|
||||
public forwardJSONCall(actionDescription: string, action: () => any): string {
|
||||
public forwardJSONCall(actionDescription: string, action: () => {}): string {
|
||||
return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance);
|
||||
}
|
||||
|
||||
|
@ -893,10 +893,13 @@ namespace ts {
|
|||
}
|
||||
|
||||
/** Get a string based representation of a completion list entry details */
|
||||
public getCompletionEntryDetails(fileName: string, position: number, entryName: string) {
|
||||
public getCompletionEntryDetails(fileName: string, position: number, entryName: string, options: string/*Services.FormatCodeOptions*/) {
|
||||
return this.forwardJSONCall(
|
||||
`getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`,
|
||||
() => this.languageService.getCompletionEntryDetails(fileName, position, entryName)
|
||||
() => {
|
||||
const localOptions: ts.FormatCodeOptions = JSON.parse(options);
|
||||
return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1030,7 +1033,7 @@ namespace ts {
|
|||
super(factory);
|
||||
}
|
||||
|
||||
private forwardJSONCall(actionDescription: string, action: () => any): any {
|
||||
private forwardJSONCall(actionDescription: string, action: () => {}): any {
|
||||
return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance);
|
||||
}
|
||||
|
||||
|
|
|
@ -186,14 +186,25 @@ namespace ts.textChanges {
|
|||
return s;
|
||||
}
|
||||
|
||||
export interface TextChangesContext {
|
||||
newLineCharacter: string;
|
||||
rulesProvider: formatting.RulesProvider;
|
||||
}
|
||||
|
||||
export class ChangeTracker {
|
||||
private changes: Change[] = [];
|
||||
private readonly newLineCharacter: string;
|
||||
|
||||
public static fromContext(context: RefactorContext | CodeFixContext) {
|
||||
public static fromContext(context: TextChangesContext): ChangeTracker {
|
||||
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider);
|
||||
}
|
||||
|
||||
public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] {
|
||||
const tracker = ChangeTracker.fromContext(context);
|
||||
cb(tracker);
|
||||
return tracker.getChanges();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly newLine: NewLineKind,
|
||||
private readonly rulesProvider: formatting.RulesProvider,
|
||||
|
|
|
@ -230,7 +230,8 @@ namespace ts {
|
|||
getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications;
|
||||
|
||||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
|
||||
// "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;
|
||||
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
|
||||
|
@ -668,6 +669,7 @@ namespace ts {
|
|||
* be used in that case
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
}
|
||||
|
||||
export interface CompletionEntryDetails {
|
||||
|
@ -677,6 +679,7 @@ namespace ts {
|
|||
displayParts: SymbolDisplayPart[];
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
}
|
||||
|
||||
export interface OutliningSpan {
|
||||
|
|
|
@ -1278,9 +1278,7 @@ namespace ts {
|
|||
*/
|
||||
export function stripQuotes(name: string) {
|
||||
const length = name.length;
|
||||
if (length >= 2 &&
|
||||
name.charCodeAt(0) === name.charCodeAt(length - 1) &&
|
||||
(name.charCodeAt(0) === CharacterCodes.doubleQuote || name.charCodeAt(0) === CharacterCodes.singleQuote)) {
|
||||
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isSingleOrDoubleQuote(name.charCodeAt(0))) {
|
||||
return name.substring(1, length - 1);
|
||||
}
|
||||
return name;
|
||||
|
@ -1297,6 +1295,10 @@ namespace ts {
|
|||
return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName));
|
||||
}
|
||||
|
||||
export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) {
|
||||
return getSymbolId(skipAlias(symbol, checker));
|
||||
}
|
||||
|
||||
export function getFirstNonSpaceCharacterPosition(text: string, position: number) {
|
||||
while (isWhiteSpaceLike(text.charCodeAt(position))) {
|
||||
position += 1;
|
||||
|
|
|
@ -3205,10 +3205,11 @@ declare namespace ts {
|
|||
};
|
||||
}
|
||||
declare namespace ts {
|
||||
function getEffectiveTypeRoots(options: CompilerOptions, host: {
|
||||
directoryExists?: (directoryName: string) => boolean;
|
||||
getCurrentDirectory?: () => string;
|
||||
}): string[] | undefined;
|
||||
interface GetEffectiveTypeRootsHost {
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
getCurrentDirectory?(): string;
|
||||
}
|
||||
function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined;
|
||||
/**
|
||||
* @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
|
||||
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
|
||||
|
@ -3911,7 +3912,7 @@ 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): CompletionEntryDetails;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
|
||||
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
|
||||
|
@ -4279,6 +4280,7 @@ declare namespace ts {
|
|||
* be used in that case
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
}
|
||||
interface CompletionEntryDetails {
|
||||
name: string;
|
||||
|
@ -4287,6 +4289,7 @@ declare namespace ts {
|
|||
displayParts: SymbolDisplayPart[];
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
}
|
||||
interface OutliningSpan {
|
||||
/** The span of the document to actually collapse. */
|
||||
|
@ -6036,6 +6039,11 @@ declare namespace ts.server.protocol {
|
|||
* this span should be used instead of the default one.
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
/**
|
||||
* Indicates whether 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;
|
||||
}
|
||||
/**
|
||||
* Additional completion entry details, available on demand
|
||||
|
@ -6065,6 +6073,10 @@ declare namespace ts.server.protocol {
|
|||
* JSDoc tags for the symbol.
|
||||
*/
|
||||
tags: JSDocTagInfo[];
|
||||
/**
|
||||
* The associated code actions for this entry
|
||||
*/
|
||||
codeActions?: CodeAction[];
|
||||
}
|
||||
interface CompletionsResponse extends Response {
|
||||
body?: CompletionEntry[];
|
||||
|
|
13
tests/baselines/reference/api/typescript.d.ts
vendored
13
tests/baselines/reference/api/typescript.d.ts
vendored
|
@ -3152,10 +3152,11 @@ declare namespace ts {
|
|||
function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile;
|
||||
}
|
||||
declare namespace ts {
|
||||
function getEffectiveTypeRoots(options: CompilerOptions, host: {
|
||||
directoryExists?: (directoryName: string) => boolean;
|
||||
getCurrentDirectory?: () => string;
|
||||
}): string[] | undefined;
|
||||
interface GetEffectiveTypeRootsHost {
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
getCurrentDirectory?(): string;
|
||||
}
|
||||
function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined;
|
||||
/**
|
||||
* @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
|
||||
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
|
||||
|
@ -3911,7 +3912,7 @@ 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): CompletionEntryDetails;
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails;
|
||||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol;
|
||||
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
|
||||
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
|
||||
|
@ -4279,6 +4280,7 @@ declare namespace ts {
|
|||
* be used in that case
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
}
|
||||
interface CompletionEntryDetails {
|
||||
name: string;
|
||||
|
@ -4287,6 +4289,7 @@ declare namespace ts {
|
|||
displayParts: SymbolDisplayPart[];
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
}
|
||||
interface OutliningSpan {
|
||||
/** The span of the document to actually collapse. */
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export default function foo() {}
|
||||
////export const x = 0;
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import { x } from "./a";
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
description: `Add 'foo' to existing import declaration from "./a".`,
|
||||
newFileContent: `import foo, { x } from "./a";
|
||||
f;`,
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export default function foo() {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import * as a from "./a";
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
description: `Add 'foo' to existing import declaration from "./a".`,
|
||||
newFileContent: `import foo, * as a from "./a";
|
||||
f;`,
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export default function foo() {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import f_o_o from "./a";
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import f_o_o from "./a";
|
||||
import foo from "./a";\r
|
||||
f;`,
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export default function foo() {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import foo from "./a";\r
|
||||
\r
|
||||
f;`,
|
||||
});
|
18
tests/cases/fourslash/completionsImport_fromAmbientModule.ts
Normal file
18
tests/cases/fourslash/completionsImport_fromAmbientModule.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////declare module "m" {
|
||||
//// export const x: number;
|
||||
////}
|
||||
|
||||
// @Filename: /b.ts
|
||||
/////**/
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "x",
|
||||
description: `Import 'x' from "m".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import { x } from "m";\r
|
||||
\r
|
||||
`,
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export function foo() {}
|
||||
////export const x = 0;
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import { x } from "./a";
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
description: `Add 'foo' to existing import declaration from "./a".`,
|
||||
newFileContent: `import { x, foo } from "./a";
|
||||
f;`,
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export function Test1() {}
|
||||
////export function Test2() {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import { Test2 } from "./a";
|
||||
////t/**/
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("Test1", "function Test1(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
verify.completionListContains("Test2", "import Test2", "", "alias", /*spanIndex*/ undefined, /*hasAction*/ undefined);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "Test1",
|
||||
description: `Add 'Test1' to existing import declaration from "./a".`,
|
||||
newFileContent: `import { Test2, Test1 } from "./a";
|
||||
t`,
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export function foo() {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import * as a from "./a";
|
||||
////f/**/;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completionListContains("foo", "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true);
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "foo",
|
||||
description: `Import 'foo' from "./a".`,
|
||||
// TODO: GH#18445
|
||||
newFileContent: `import * as a from "./a";
|
||||
import { foo } from "./a";\r
|
||||
f;`,
|
||||
});
|
|
@ -140,7 +140,14 @@ declare namespace FourSlashInterface {
|
|||
allowedConstructorParameterKeywords: string[];
|
||||
constructor(negative?: boolean);
|
||||
completionListCount(expectedCount: number): void;
|
||||
completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number): void;
|
||||
completionListContains(
|
||||
symbol: string,
|
||||
text?: string,
|
||||
documentation?: string,
|
||||
kind?: string,
|
||||
spanIndex?: number,
|
||||
hasAction?: boolean,
|
||||
): void;
|
||||
completionListItemsCountIsGreaterThan(count: number): void;
|
||||
completionListIsEmpty(): void;
|
||||
completionListContainsClassElementKeywords(): void;
|
||||
|
@ -173,6 +180,20 @@ declare namespace FourSlashInterface {
|
|||
assertHasRanges(ranges: Range[]): void;
|
||||
caretAtMarker(markerName?: string): void;
|
||||
completionsAt(markerName: string, completions: string[], options?: { isNewIdentifierLocation?: boolean }): void;
|
||||
completionsAndDetailsAt(
|
||||
markerName: string,
|
||||
completions: {
|
||||
excludes?: ReadonlyArray<string>,
|
||||
//TODO: better type
|
||||
entries: ReadonlyArray<{ entry: any, details: any }>,
|
||||
},
|
||||
): void; //TODO: better type
|
||||
applyCodeActionFromCompletion(markerName: string, options: {
|
||||
name: string,
|
||||
description: string,
|
||||
newFileContent?: string,
|
||||
newRangeContent?: string,
|
||||
});
|
||||
indentationIs(numberOfSpaces: number): void;
|
||||
indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle?: ts.IndentStyle, baseIndentSize?: number): void;
|
||||
textAtCaretIs(text: string): void;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//// export function foo() {};
|
||||
|
||||
// @Filename: a/foo.ts
|
||||
//// export { foo } from "./foo/bar";
|
||||
//// export { foo } from "./foo/bar";
|
||||
|
||||
verify.importFixAtPosition([
|
||||
`import * as ns from "./foo";
|
||||
|
|
Loading…
Reference in a new issue