mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'AddDefinitionAndBoundSpan' of https://github.com/armanio123/TypeScript into AddDefinitionAndBoundSpan
This commit is contained in:
commit
c62f2880cb
313 files changed
+120671
-112683
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
registerCodeFix({
|
||||
errorCodes: [
|
||||
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code,
|
||||
],
|
||||
getCodeActions: context => {
|
||||
const { sourceFile, span: { start } } = context;
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
if (!isStringLiteral(token)) {
|
||||
throw Debug.fail(); // These errors should only happen on the module name.
|
||||
}
|
||||
|
||||
const action = tryGetCodeActionForInstallPackageTypes(context.host, token.text);
|
||||
return action && [action];
|
||||
},
|
||||
});
|
||||
|
||||
export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, moduleName: string): CodeAction | undefined {
|
||||
const { packageName } = getPackageName(moduleName);
|
||||
|
||||
if (!host.isKnownTypesPackageName(packageName)) {
|
||||
// If !registry, registry not available yet, can't do anything.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typesPackageName = getTypesPackageName(packageName);
|
||||
return {
|
||||
description: `Install '${typesPackageName}'`,
|
||||
changes: [],
|
||||
commands: [{ type: "install package", packageName: typesPackageName }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
/// <reference path="fixClassIncorrectlyImplementsInterface.ts" />
|
||||
/// <reference path="fixAddMissingMember.ts" />
|
||||
/// <reference path="fixSpelling.ts" />
|
||||
/// <reference path="fixCannotFindModule.ts" />
|
||||
/// <reference path="fixClassDoesntImplementInheritedAbstractMember.ts" />
|
||||
/// <reference path="fixClassSuperMustPrecedeThisAccess.ts" />
|
||||
/// <reference path="fixConstructorForDerivedNeedSuperCall.ts" />
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -521,7 +521,7 @@ namespace ts.codefix {
|
||||
if (!usageContext.properties) {
|
||||
usageContext.properties = createUnderscoreEscapedMap<UsageContext>();
|
||||
}
|
||||
const propertyUsageContext = {};
|
||||
const propertyUsageContext = usageContext.properties.get(name) || { };
|
||||
inferTypeFromContext(parent, checker, propertyUsageContext);
|
||||
usageContext.properties.set(name, propertyUsageContext);
|
||||
}
|
||||
@@ -575,7 +575,7 @@ namespace ts.codefix {
|
||||
if (usageContext.properties) {
|
||||
usageContext.properties.forEach((context, name) => {
|
||||
const symbol = checker.createSymbol(SymbolFlags.Property, name);
|
||||
symbol.type = getTypeFromUsageContext(context, checker);
|
||||
symbol.type = getTypeFromUsageContext(context, checker) || checker.getAnyType();
|
||||
members.set(name, symbol);
|
||||
});
|
||||
}
|
||||
@@ -636,7 +636,7 @@ namespace ts.codefix {
|
||||
symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i]));
|
||||
parameters.push(symbol);
|
||||
}
|
||||
const returnType = getTypeFromUsageContext(callContext.returnType, checker);
|
||||
const returnType = getTypeFromUsageContext(callContext.returnType, checker) || checker.getVoidType();
|
||||
return checker.createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, callContext.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
|
||||
}
|
||||
|
||||
|
||||
+174
-32
@@ -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 && isIdentifier(previousToken) ? previousToken.text : "");
|
||||
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,59 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string): void {
|
||||
const tokenTextLowerCase = tokenText.toLowerCase();
|
||||
const symbolIdMap = arrayToNumericMap(symbols, s => getUniqueSymbolId(s, typeChecker));
|
||||
|
||||
codefix.forEachExternalModule(typeChecker, allSourceFiles, moduleSymbol => {
|
||||
if (moduleSymbol === sourceFile.symbol) {
|
||||
return;
|
||||
}
|
||||
|
||||
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] && stringContainsCharactersInOrder(name.toLowerCase(), tokenTextLowerCase)) {
|
||||
symbols.push(symbol);
|
||||
symbolToOriginInfoMap[id] = { moduleSymbol, isDefaultExport };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* True if you could remove some characters in `a` to get `b`.
|
||||
* E.g., true for "abcdef" and "bdf".
|
||||
* But not true for "abcdef" and "dbf".
|
||||
*/
|
||||
function stringContainsCharactersInOrder(str: string, characters: string): boolean {
|
||||
if (characters.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let characterIndex = 0;
|
||||
for (let strIndex = 0; strIndex < str.length; strIndex++) {
|
||||
if (str.charCodeAt(strIndex) === characters.charCodeAt(characterIndex)) {
|
||||
characterIndex++;
|
||||
if (characterIndex === characters.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Did not find all characters
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first node that "embraces" the position, so that one may
|
||||
* accurately aggregate locals from the closest containing scope.
|
||||
@@ -1627,7 +1769,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;
|
||||
|
||||
@@ -377,19 +377,26 @@ namespace ts.FindAllReferences.Core {
|
||||
|
||||
const result: SymbolAndEntries[] = [];
|
||||
const state = new State(sourceFiles, /*isForConstructor*/ node.kind === SyntaxKind.ConstructorKeyword, checker, cancellationToken, searchMeaning, options, result);
|
||||
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: populateSearchSymbolSet(symbol, node, checker, options.implementations) });
|
||||
|
||||
// Try to get the smallest valid scope that we can limit our search to;
|
||||
// otherwise we'll need to search globally (i.e. include each file).
|
||||
const scope = getSymbolScope(symbol);
|
||||
if (scope) {
|
||||
getReferencesInContainer(scope, scope.getSourceFile(), search, state);
|
||||
if (node.kind === SyntaxKind.DefaultKeyword) {
|
||||
addReference(node, symbol, node, state);
|
||||
searchForImportsOfExport(node, symbol, { exportingModuleSymbol: symbol.parent, exportKind: ExportKind.Default }, state);
|
||||
}
|
||||
else {
|
||||
// Global search
|
||||
for (const sourceFile of state.sourceFiles) {
|
||||
state.cancellationToken.throwIfCancellationRequested();
|
||||
searchForName(sourceFile, search, state);
|
||||
const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: populateSearchSymbolSet(symbol, node, checker, options.implementations) });
|
||||
|
||||
// Try to get the smallest valid scope that we can limit our search to;
|
||||
// otherwise we'll need to search globally (i.e. include each file).
|
||||
const scope = getSymbolScope(symbol);
|
||||
if (scope) {
|
||||
getReferencesInContainer(scope, scope.getSourceFile(), search, state);
|
||||
}
|
||||
else {
|
||||
// Global search
|
||||
for (const sourceFile of state.sourceFiles) {
|
||||
state.cancellationToken.throwIfCancellationRequested();
|
||||
searchForName(sourceFile, search, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,19 @@ namespace ts.formatting {
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed indentation for a given position in source file
|
||||
* @param position - position in file
|
||||
* @param sourceFile - target source file
|
||||
* @param options - set of editor options that control indentation
|
||||
* @param assumeNewLineBeforeCloseBrace - false when getIndentation is called on the text from the real source file.
|
||||
* true - when we need to assume that position is on the newline. This is usefult for codefixes, i.e.
|
||||
* @param assumeNewLineBeforeCloseBrace
|
||||
* `false` when called on text from a real source file.
|
||||
* `true` when we need to assume `position` is on a newline.
|
||||
*
|
||||
* This is useful for codefixes. Consider
|
||||
* ```
|
||||
* function f() {
|
||||
* |}
|
||||
* when inserting some text after open brace we would like to get the value of indentation as if newline was already there.
|
||||
* However by default indentation at position | will be 0 so 'assumeNewLineBeforeCloseBrace' allows to override this behavior,
|
||||
* ```
|
||||
* with `position` at `|`.
|
||||
*
|
||||
* When inserting some text after an open brace, we would like to get indentation as if a newline was already there.
|
||||
* By default indentation at `position` will be 0 so 'assumeNewLineBeforeCloseBrace' overrides this behavior.
|
||||
*/
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number {
|
||||
if (position > sourceFile.text.length) {
|
||||
@@ -157,10 +160,11 @@ namespace ts.formatting {
|
||||
options: EditorSettings): number {
|
||||
|
||||
let parent: Node = current.parent;
|
||||
let parentStart: LineAndCharacter;
|
||||
let containingListOrParentStart: LineAndCharacter;
|
||||
|
||||
// walk upwards and collect indentations for pairs of parent-child nodes
|
||||
// indentation is not added if parent and child nodes start on the same line or if parent is IfStatement and child starts on the same line with 'else clause'
|
||||
// Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if
|
||||
// * parent and child nodes start on the same line, or
|
||||
// * parent is an IfStatement and child starts on the same line as an 'else clause'.
|
||||
while (parent) {
|
||||
let useActualIndentation = true;
|
||||
if (ignoreActualIndentationRange) {
|
||||
@@ -175,9 +179,10 @@ namespace ts.formatting {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
}
|
||||
parentStart = getParentStart(parent, current, sourceFile);
|
||||
|
||||
containingListOrParentStart = getContainingListOrParentStart(parent, current, sourceFile);
|
||||
const parentAndChildShareLine =
|
||||
parentStart.line === currentStart.line ||
|
||||
containingListOrParentStart.line === currentStart.line ||
|
||||
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile);
|
||||
|
||||
if (useActualIndentation) {
|
||||
@@ -197,22 +202,30 @@ namespace ts.formatting {
|
||||
indentationDelta += options.indentSize;
|
||||
}
|
||||
|
||||
// In our AST, a call argument's `parent` is the call-expression, not the argument list.
|
||||
// We would like to increase indentation based on the relationship between an argument and its argument-list,
|
||||
// so we spoof the starting position of the (parent) call-expression to match the (non-parent) argument-list.
|
||||
// But, the spoofed start-value could then cause a problem when comparing the start position of the call-expression
|
||||
// to *its* parent (in the case of an iife, an expression statement), adding an extra level of indentation.
|
||||
//
|
||||
// Instead, when at an argument, we unspoof the starting position of the enclosing call expression
|
||||
// *after* applying indentation for the argument.
|
||||
|
||||
const useTrueStart =
|
||||
isArgumentAndStartLineOverlapsExpressionBeingCalled(parent, current, currentStart.line, sourceFile);
|
||||
|
||||
current = parent;
|
||||
currentStart = parentStart;
|
||||
parent = current.parent;
|
||||
currentStart = useTrueStart ? sourceFile.getLineAndCharacterOfPosition(current.getStart()) : containingListOrParentStart;
|
||||
}
|
||||
|
||||
return indentationDelta + getBaseIndentation(options);
|
||||
}
|
||||
|
||||
|
||||
function getParentStart(parent: Node, child: Node, sourceFile: SourceFile): LineAndCharacter {
|
||||
function getContainingListOrParentStart(parent: Node, child: Node, sourceFile: SourceFile): LineAndCharacter {
|
||||
const containingList = getContainingList(child, sourceFile);
|
||||
if (containingList) {
|
||||
return sourceFile.getLineAndCharacterOfPosition(containingList.pos);
|
||||
}
|
||||
|
||||
return sourceFile.getLineAndCharacterOfPosition(parent.getStart(sourceFile));
|
||||
const startPos = containingList ? containingList.pos : parent.getStart(sourceFile);
|
||||
return sourceFile.getLineAndCharacterOfPosition(startPos);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -291,6 +304,16 @@ namespace ts.formatting {
|
||||
return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));
|
||||
}
|
||||
|
||||
export function isArgumentAndStartLineOverlapsExpressionBeingCalled(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFileLike): boolean {
|
||||
if (!(isCallExpression(parent) && contains(parent.arguments, child))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expressionOfCallExpressionEnd = parent.expression.getEnd();
|
||||
const expressionOfCallExpressionEndLine = getLineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd).line;
|
||||
return expressionOfCallExpressionEndLine === childStartLine;
|
||||
}
|
||||
|
||||
export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean {
|
||||
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
|
||||
const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile);
|
||||
|
||||
@@ -246,4 +246,65 @@ namespace ts.JsTyping {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const enum PackageNameValidationResult {
|
||||
Ok,
|
||||
ScopedPackagesNotSupported,
|
||||
EmptyName,
|
||||
NameTooLong,
|
||||
NameStartsWithDot,
|
||||
NameStartsWithUnderscore,
|
||||
NameContainsNonURISafeCharacters
|
||||
}
|
||||
|
||||
const MaxPackageNameLength = 214;
|
||||
|
||||
/**
|
||||
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
|
||||
*/
|
||||
export function validatePackageName(packageName: string): PackageNameValidationResult {
|
||||
if (!packageName) {
|
||||
return PackageNameValidationResult.EmptyName;
|
||||
}
|
||||
if (packageName.length > MaxPackageNameLength) {
|
||||
return PackageNameValidationResult.NameTooLong;
|
||||
}
|
||||
if (packageName.charCodeAt(0) === CharacterCodes.dot) {
|
||||
return PackageNameValidationResult.NameStartsWithDot;
|
||||
}
|
||||
if (packageName.charCodeAt(0) === CharacterCodes._) {
|
||||
return PackageNameValidationResult.NameStartsWithUnderscore;
|
||||
}
|
||||
// check if name is scope package like: starts with @ and has one '/' in the middle
|
||||
// scoped packages are not currently supported
|
||||
// TODO: when support will be added we'll need to split and check both scope and package name
|
||||
if (/^@[^/]+\/[^/]+$/.test(packageName)) {
|
||||
return PackageNameValidationResult.ScopedPackagesNotSupported;
|
||||
}
|
||||
if (encodeURIComponent(packageName) !== packageName) {
|
||||
return PackageNameValidationResult.NameContainsNonURISafeCharacters;
|
||||
}
|
||||
return PackageNameValidationResult.Ok;
|
||||
}
|
||||
|
||||
export function renderPackageNameValidationFailure(result: PackageNameValidationResult, typing: string): string {
|
||||
switch (result) {
|
||||
case PackageNameValidationResult.EmptyName:
|
||||
return `Package name '${typing}' cannot be empty`;
|
||||
case PackageNameValidationResult.NameTooLong:
|
||||
return `Package name '${typing}' should be less than ${MaxPackageNameLength} characters`;
|
||||
case PackageNameValidationResult.NameStartsWithDot:
|
||||
return `Package name '${typing}' cannot start with '.'`;
|
||||
case PackageNameValidationResult.NameStartsWithUnderscore:
|
||||
return `Package name '${typing}' cannot start with '_'`;
|
||||
case PackageNameValidationResult.ScopedPackagesNotSupported:
|
||||
return `Package '${typing}' is scoped and currently is not supported`;
|
||||
case PackageNameValidationResult.NameContainsNonURISafeCharacters:
|
||||
return `Package name '${typing}' contains non URI safe characters`;
|
||||
case PackageNameValidationResult.Ok:
|
||||
throw Debug.fail(); // Shouldn't have called this.
|
||||
default:
|
||||
Debug.assertNever(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,12 @@ 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;
|
||||
host: LanguageServiceHost;
|
||||
cancellationToken?: CancellationToken;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/* @internal */
|
||||
namespace ts.refactor.annotateWithTypeFromJSDoc {
|
||||
const actionName = "annotate";
|
||||
|
||||
const annotateTypeFromJSDoc: Refactor = {
|
||||
name: "Annotate with type from JSDoc",
|
||||
description: Diagnostics.Annotate_with_type_from_JSDoc.message,
|
||||
getEditsForAction,
|
||||
getAvailableActions
|
||||
};
|
||||
type DeclarationWithType =
|
||||
| FunctionLikeDeclaration
|
||||
| VariableDeclaration
|
||||
| ParameterDeclaration
|
||||
| PropertySignature
|
||||
| PropertyDeclaration;
|
||||
|
||||
registerRefactor(annotateTypeFromJSDoc);
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
if (isInJavaScriptFile(context.file)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false);
|
||||
if (hasUsableJSDoc(findAncestor(node, isDeclarationWithType))) {
|
||||
return [{
|
||||
name: annotateTypeFromJSDoc.name,
|
||||
description: annotateTypeFromJSDoc.description,
|
||||
actions: [
|
||||
{
|
||||
description: annotateTypeFromJSDoc.description,
|
||||
name: actionName
|
||||
}
|
||||
]
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
function hasUsableJSDoc(decl: DeclarationWithType): boolean {
|
||||
if (!decl) {
|
||||
return false;
|
||||
}
|
||||
if (isFunctionLikeDeclaration(decl)) {
|
||||
return decl.parameters.some(hasUsableJSDoc) || (!decl.type && !!getJSDocReturnType(decl));
|
||||
}
|
||||
return !decl.type && !!getJSDocType(decl);
|
||||
}
|
||||
|
||||
function getEditsForAction(context: RefactorContext, action: string): RefactorEditInfo | undefined {
|
||||
if (actionName !== action) {
|
||||
return Debug.fail(`actionName !== action: ${actionName} !== ${action}`);
|
||||
}
|
||||
const node = getTokenAtPosition(context.file, context.startPosition, /*includeJsDocComment*/ false);
|
||||
const decl = findAncestor(node, isDeclarationWithType);
|
||||
if (!decl || decl.type) {
|
||||
return undefined;
|
||||
}
|
||||
const jsdocType = getJSDocType(decl);
|
||||
const isFunctionWithJSDoc = isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p)));
|
||||
if (isFunctionWithJSDoc || jsdocType && decl.kind === SyntaxKind.Parameter) {
|
||||
return getEditsForFunctionAnnotation(context);
|
||||
}
|
||||
else if (jsdocType) {
|
||||
return getEditsForAnnotation(context);
|
||||
}
|
||||
else {
|
||||
Debug.assert(!!refactor, "No applicable refactor found.");
|
||||
}
|
||||
}
|
||||
|
||||
function getEditsForAnnotation(context: RefactorContext): RefactorEditInfo | undefined {
|
||||
const sourceFile = context.file;
|
||||
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false);
|
||||
const decl = findAncestor(token, isDeclarationWithType);
|
||||
const jsdocType = getJSDocType(decl);
|
||||
if (!decl || !jsdocType || decl.type) {
|
||||
return Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`);
|
||||
}
|
||||
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
const declarationWithType = addType(decl, transformJSDocType(jsdocType) as TypeNode);
|
||||
suppressLeadingAndTrailingTrivia(declarationWithType);
|
||||
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, declarationWithType);
|
||||
return {
|
||||
edits: changeTracker.getChanges(),
|
||||
renameFilename: undefined,
|
||||
renameLocation: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function getEditsForFunctionAnnotation(context: RefactorContext): RefactorEditInfo | undefined {
|
||||
const sourceFile = context.file;
|
||||
const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false);
|
||||
const decl = findAncestor(token, isFunctionLikeDeclaration);
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
const functionWithType = addTypesToFunctionLike(decl);
|
||||
suppressLeadingAndTrailingTrivia(functionWithType);
|
||||
changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, functionWithType);
|
||||
return {
|
||||
edits: changeTracker.getChanges(),
|
||||
renameFilename: undefined,
|
||||
renameLocation: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function isDeclarationWithType(node: Node): node is DeclarationWithType {
|
||||
return isFunctionLikeDeclaration(node) ||
|
||||
node.kind === SyntaxKind.VariableDeclaration ||
|
||||
node.kind === SyntaxKind.Parameter ||
|
||||
node.kind === SyntaxKind.PropertySignature ||
|
||||
node.kind === SyntaxKind.PropertyDeclaration;
|
||||
}
|
||||
|
||||
function addTypesToFunctionLike(decl: FunctionLikeDeclaration) {
|
||||
const typeParameters = getEffectiveTypeParameterDeclarations(decl, /*checkJSDoc*/ true);
|
||||
const parameters = decl.parameters.map(
|
||||
p => createParameter(p.decorators, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, transformJSDocType(getEffectiveTypeAnnotationNode(p, /*checkJSDoc*/ true)) as TypeNode, p.initializer));
|
||||
const returnType = transformJSDocType(getEffectiveReturnTypeNode(decl, /*checkJSDoc*/ true)) as TypeNode;
|
||||
switch (decl.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
return createFunctionDeclaration(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, typeParameters, parameters, returnType, decl.body);
|
||||
case SyntaxKind.Constructor:
|
||||
return createConstructor(decl.decorators, decl.modifiers, parameters, decl.body);
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return createFunctionExpression(decl.modifiers, decl.asteriskToken, (decl as FunctionExpression).name, typeParameters, parameters, returnType, decl.body);
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return createArrowFunction(decl.modifiers, typeParameters, parameters, returnType, decl.equalsGreaterThanToken, decl.body);
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return createMethod(decl.decorators, decl.modifiers, decl.asteriskToken, decl.name, decl.questionToken, typeParameters, parameters, returnType, decl.body);
|
||||
case SyntaxKind.GetAccessor:
|
||||
return createGetAccessor(decl.decorators, decl.modifiers, decl.name, decl.parameters, returnType, decl.body);
|
||||
case SyntaxKind.SetAccessor:
|
||||
return createSetAccessor(decl.decorators, decl.modifiers, decl.name, parameters, decl.body);
|
||||
default:
|
||||
return Debug.assertNever(decl, `Unexpected SyntaxKind: ${(decl as any).kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
function addType(decl: DeclarationWithType, jsdocType: TypeNode) {
|
||||
switch (decl.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return createVariableDeclaration(decl.name, jsdocType, decl.initializer);
|
||||
case SyntaxKind.PropertySignature:
|
||||
return createPropertySignature(decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer);
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
return createProperty(decl.decorators, decl.modifiers, decl.name, decl.questionToken, jsdocType, decl.initializer);
|
||||
default:
|
||||
return Debug.fail(`Unexpected SyntaxKind: ${decl.kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
function transformJSDocType(node: Node): Node | undefined {
|
||||
if (node === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.JSDocAllType:
|
||||
case SyntaxKind.JSDocUnknownType:
|
||||
return createTypeReferenceNode("any", emptyArray);
|
||||
case SyntaxKind.JSDocOptionalType:
|
||||
return transformJSDocOptionalType(node as JSDocOptionalType);
|
||||
case SyntaxKind.JSDocNonNullableType:
|
||||
return transformJSDocType((node as JSDocNonNullableType).type);
|
||||
case SyntaxKind.JSDocNullableType:
|
||||
return transformJSDocNullableType(node as JSDocNullableType);
|
||||
case SyntaxKind.JSDocVariadicType:
|
||||
return transformJSDocVariadicType(node as JSDocVariadicType);
|
||||
case SyntaxKind.JSDocFunctionType:
|
||||
return transformJSDocFunctionType(node as JSDocFunctionType);
|
||||
case SyntaxKind.Parameter:
|
||||
return transformJSDocParameter(node as ParameterDeclaration);
|
||||
case SyntaxKind.TypeReference:
|
||||
return transformJSDocTypeReference(node as TypeReferenceNode);
|
||||
default:
|
||||
const visited = visitEachChild(node, transformJSDocType, /*context*/ undefined) as TypeNode;
|
||||
setEmitFlags(visited, EmitFlags.SingleLine);
|
||||
return visited;
|
||||
}
|
||||
}
|
||||
|
||||
function transformJSDocOptionalType(node: JSDocOptionalType) {
|
||||
return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("undefined", emptyArray)]);
|
||||
}
|
||||
|
||||
function transformJSDocNullableType(node: JSDocNullableType) {
|
||||
return createUnionTypeNode([visitNode(node.type, transformJSDocType), createTypeReferenceNode("null", emptyArray)]);
|
||||
}
|
||||
|
||||
function transformJSDocVariadicType(node: JSDocVariadicType) {
|
||||
return createArrayTypeNode(visitNode(node.type, transformJSDocType));
|
||||
}
|
||||
|
||||
function transformJSDocFunctionType(node: JSDocFunctionType) {
|
||||
const parameters = node.parameters && node.parameters.map(transformJSDocType);
|
||||
return createFunctionTypeNode(emptyArray, parameters as ParameterDeclaration[], node.type);
|
||||
}
|
||||
|
||||
function transformJSDocParameter(node: ParameterDeclaration) {
|
||||
const index = node.parent.parameters.indexOf(node);
|
||||
const isRest = node.type.kind === SyntaxKind.JSDocVariadicType && index === node.parent.parameters.length - 1;
|
||||
const name = node.name || (isRest ? "rest" : "arg" + index);
|
||||
const dotdotdot = isRest ? createToken(SyntaxKind.DotDotDotToken) : node.dotDotDotToken;
|
||||
return createParameter(node.decorators, node.modifiers, dotdotdot, name, node.questionToken, visitNode(node.type, transformJSDocType), node.initializer);
|
||||
}
|
||||
|
||||
function transformJSDocTypeReference(node: TypeReferenceNode) {
|
||||
let name = node.typeName;
|
||||
let args = node.typeArguments;
|
||||
if (isIdentifier(node.typeName)) {
|
||||
if (isJSDocIndexSignature(node)) {
|
||||
return transformJSDocIndexSignature(node);
|
||||
}
|
||||
let text = node.typeName.text;
|
||||
switch (node.typeName.text) {
|
||||
case "String":
|
||||
case "Boolean":
|
||||
case "Object":
|
||||
case "Number":
|
||||
text = text.toLowerCase();
|
||||
break;
|
||||
case "array":
|
||||
case "date":
|
||||
case "promise":
|
||||
text = text[0].toUpperCase() + text.slice(1);
|
||||
break;
|
||||
}
|
||||
name = createIdentifier(text);
|
||||
if ((text === "Array" || text === "Promise") && !node.typeArguments) {
|
||||
args = createNodeArray([createTypeReferenceNode("any", emptyArray)]);
|
||||
}
|
||||
else {
|
||||
args = visitNodes(node.typeArguments, transformJSDocType);
|
||||
}
|
||||
}
|
||||
return createTypeReferenceNode(name, args);
|
||||
}
|
||||
|
||||
function transformJSDocIndexSignature(node: TypeReferenceNode) {
|
||||
const index = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
node.typeArguments[0].kind === SyntaxKind.NumberKeyword ? "n" : "s",
|
||||
/*questionToken*/ undefined,
|
||||
createTypeReferenceNode(node.typeArguments[0].kind === SyntaxKind.NumberKeyword ? "number" : "string", []),
|
||||
/*initializer*/ undefined);
|
||||
const indexSignature = createTypeLiteralNode([createIndexSignature(/*decorators*/ undefined, /*modifiers*/ undefined, [index], node.typeArguments[1])]);
|
||||
setEmitFlags(indexSignature, EmitFlags.SingleLine);
|
||||
return indexSignature;
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,16 @@ namespace ts.refactor.convertFunctionToES6Class {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const start = context.startPosition;
|
||||
const node = getTokenAtPosition(context.file, start, /*includeJsDocComment*/ false);
|
||||
const checker = context.program.getTypeChecker();
|
||||
let symbol = checker.getSymbolAtLocation(node);
|
||||
let symbol = getConstructorSymbol(context);
|
||||
if (!symbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (symbol && isDeclarationOfFunctionOrClassExpression(symbol)) {
|
||||
if (isDeclarationOfFunctionOrClassExpression(symbol)) {
|
||||
symbol = (symbol.valueDeclaration as VariableDeclaration).initializer.symbol;
|
||||
}
|
||||
|
||||
if (symbol && (symbol.flags & SymbolFlags.Function) && symbol.members && (symbol.members.size > 0)) {
|
||||
if ((symbol.flags & SymbolFlags.Function) && symbol.members && (symbol.members.size > 0)) {
|
||||
return [
|
||||
{
|
||||
name: convertFunctionToES6Class.name,
|
||||
@@ -48,11 +48,8 @@ namespace ts.refactor.convertFunctionToES6Class {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const start = context.startPosition;
|
||||
const sourceFile = context.file;
|
||||
const checker = context.program.getTypeChecker();
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const ctorSymbol = checker.getSymbolAtLocation(token);
|
||||
const { file: sourceFile } = context;
|
||||
const ctorSymbol = getConstructorSymbol(context);
|
||||
const newLine = context.rulesProvider.getFormatOptions().newLineCharacter;
|
||||
|
||||
const deletedNodes: Node[] = [];
|
||||
@@ -269,4 +266,10 @@ namespace ts.refactor.convertFunctionToES6Class {
|
||||
return filter(source.modifiers, modifier => modifier.kind === kind);
|
||||
}
|
||||
}
|
||||
|
||||
function getConstructorSymbol({ startPosition, file, program }: RefactorContext): Symbol {
|
||||
const checker = program.getTypeChecker();
|
||||
const token = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
|
||||
return checker.getSymbolAtLocation(token);
|
||||
}
|
||||
}
|
||||
@@ -470,7 +470,7 @@ namespace ts.refactor.extractSymbol {
|
||||
* you may be able to extract into a class method *or* local closure *or* namespace function,
|
||||
* depending on what's in the extracted body.
|
||||
*/
|
||||
function collectEnclosingScopes(range: TargetRange): Scope[] | undefined {
|
||||
function collectEnclosingScopes(range: TargetRange): Scope[] {
|
||||
let current: Node = isReadonlyArray(range.range) ? first(range.range) : range.range;
|
||||
if (range.facts & RangeFacts.UsesThis) {
|
||||
// if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class
|
||||
@@ -483,30 +483,27 @@ namespace ts.refactor.extractSymbol {
|
||||
}
|
||||
}
|
||||
|
||||
const start = current;
|
||||
const scopes: Scope[] = [];
|
||||
while (true) {
|
||||
current = current.parent;
|
||||
// A function parameter's initializer is actually in the outer scope, not the function declaration
|
||||
if (current.kind === SyntaxKind.Parameter) {
|
||||
// Skip all the way to the outer scope of the function that declared this parameter
|
||||
current = findAncestor(current, parent => isFunctionLikeDeclaration(parent)).parent;
|
||||
}
|
||||
|
||||
let scopes: Scope[] | undefined = undefined;
|
||||
while (current) {
|
||||
// We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of.
|
||||
// Walk up to the closest parent of a place where we can logically put a sibling:
|
||||
// * Function declaration
|
||||
// * Class declaration or expression
|
||||
// * Module/namespace or source file
|
||||
if (current !== start && isScope(current)) {
|
||||
(scopes = scopes || []).push(current);
|
||||
if (isScope(current)) {
|
||||
scopes.push(current);
|
||||
if (current.kind === SyntaxKind.SourceFile) {
|
||||
return scopes;
|
||||
}
|
||||
}
|
||||
|
||||
// A function parameter's initializer is actually in the outer scope, not the function declaration
|
||||
if (current && current.parent && current.parent.kind === SyntaxKind.Parameter) {
|
||||
// Skip all the way to the outer scope of the function that declared this parameter
|
||||
current = findAncestor(current, parent => isFunctionLikeDeclaration(parent)).parent;
|
||||
}
|
||||
else {
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
}
|
||||
return scopes;
|
||||
}
|
||||
|
||||
function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo {
|
||||
@@ -592,15 +589,7 @@ namespace ts.refactor.extractSymbol {
|
||||
function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } {
|
||||
const { file: sourceFile } = context;
|
||||
|
||||
if (targetRange === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const scopes = collectEnclosingScopes(targetRange);
|
||||
if (scopes === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile);
|
||||
const readsAndWrites = collectReadsAndWrites(
|
||||
targetRange,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* @internal */
|
||||
namespace ts.refactor.installTypesForPackage {
|
||||
const actionName = "install";
|
||||
|
||||
const installTypesForPackage: Refactor = {
|
||||
name: "Install missing types package",
|
||||
description: "Install missing types package",
|
||||
getEditsForAction,
|
||||
getAvailableActions,
|
||||
};
|
||||
|
||||
registerRefactor(installTypesForPackage);
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
const options = context.program.getCompilerOptions();
|
||||
if (options.noImplicitAny || options.strict) {
|
||||
// Then it will be available via `fixCannotFindModule`.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const action = getAction(context);
|
||||
return action && [
|
||||
{
|
||||
name: installTypesForPackage.name,
|
||||
description: installTypesForPackage.description,
|
||||
actions: [
|
||||
{
|
||||
description: action.description,
|
||||
name: actionName,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
|
||||
Debug.assertEqual(actionName, _actionName);
|
||||
const action = getAction(context)!; // Should be defined if we said there was an action available.
|
||||
return {
|
||||
edits: [],
|
||||
renameFilename: undefined,
|
||||
renameLocation: undefined,
|
||||
commands: action.commands,
|
||||
};
|
||||
}
|
||||
|
||||
function getAction(context: RefactorContext): CodeAction | undefined {
|
||||
const { file, startPosition } = context;
|
||||
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
|
||||
if (isStringLiteral(node) && isModuleIdentifier(node) && getResolvedModule(file, node.text) === undefined) {
|
||||
return codefix.tryGetCodeActionForInstallPackageTypes(context.host, node.text);
|
||||
}
|
||||
}
|
||||
|
||||
function isModuleIdentifier(node: StringLiteral): boolean {
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ExternalModuleReference:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
/// <reference path="annotateWithTypeFromJSDoc.ts" />
|
||||
/// <reference path="convertFunctionToEs6Class.ts" />
|
||||
/// <reference path="extractSymbol.ts" />
|
||||
/// <reference path="installTypesForPackage.ts" />
|
||||
@@ -1146,7 +1146,7 @@ namespace ts {
|
||||
getCancellationToken: () => cancellationToken,
|
||||
getCanonicalFileName,
|
||||
useCaseSensitiveFileNames: () => useCaseSensitivefileNames,
|
||||
getNewLine: () => getNewLineOrDefaultFromHost(host),
|
||||
getNewLine: () => getNewLineCharacter(newSettings, { newLine: getNewLineOrDefaultFromHost(host) }),
|
||||
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
|
||||
writeFile: noop,
|
||||
getCurrentDirectory: () => currentDirectory,
|
||||
@@ -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 {
|
||||
@@ -1514,12 +1516,12 @@ namespace ts {
|
||||
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
|
||||
}
|
||||
|
||||
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) {
|
||||
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean) {
|
||||
synchronizeHostData();
|
||||
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
|
||||
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers);
|
||||
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers);
|
||||
}
|
||||
|
||||
// Signature help
|
||||
@@ -1766,6 +1768,19 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult> {
|
||||
fileName = toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
switch (action.type) {
|
||||
case "install package":
|
||||
return host.installPackage
|
||||
? host.installPackage({ fileName, packageName: action.packageName })
|
||||
: Promise.reject("Host does not implement `installPackage`");
|
||||
default:
|
||||
Debug.fail();
|
||||
// TODO: Debug.assertNever(action); will only work if there is more than one type.
|
||||
}
|
||||
}
|
||||
|
||||
function getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
|
||||
return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position);
|
||||
}
|
||||
@@ -1975,8 +1990,9 @@ namespace ts {
|
||||
endPosition,
|
||||
program: getProgram(),
|
||||
newLineCharacter: formatOptions ? formatOptions.newLineCharacter : host.getNewLine(),
|
||||
host,
|
||||
rulesProvider: getRuleProvider(formatOptions),
|
||||
cancellationToken
|
||||
cancellationToken,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2039,6 +2055,7 @@ namespace ts {
|
||||
isValidBraceCompletionAtPosition,
|
||||
getSpanOfEnclosingComment,
|
||||
getCodeFixesAtPosition,
|
||||
applyCodeActionCommand,
|
||||
getEmitOutput,
|
||||
getNonBoundSourceFile,
|
||||
getSourceFile,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -906,10 +906,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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1254,4 +1257,4 @@ namespace TypeScript.Services {
|
||||
// TODO: it should be moved into a namespace though.
|
||||
|
||||
/* @internal */
|
||||
const toolsVersion = "2.6";
|
||||
const toolsVersion = ts.versionMajorMinor;
|
||||
@@ -6,25 +6,25 @@ namespace ts.textChanges {
|
||||
* It can be changed to side-table later if we decide that current design is too invasive.
|
||||
*/
|
||||
function getPos(n: TextRange): number {
|
||||
const result = (<any>n)["__pos"];
|
||||
const result = (<any>n).__pos;
|
||||
Debug.assert(typeof result === "number");
|
||||
return result;
|
||||
}
|
||||
|
||||
function setPos(n: TextRange, pos: number): void {
|
||||
Debug.assert(typeof pos === "number");
|
||||
(<any>n)["__pos"] = pos;
|
||||
(<any>n).__pos = pos;
|
||||
}
|
||||
|
||||
function getEnd(n: TextRange): number {
|
||||
const result = (<any>n)["__end"];
|
||||
const result = (<any>n).__end;
|
||||
Debug.assert(typeof result === "number");
|
||||
return result;
|
||||
}
|
||||
|
||||
function setEnd(n: TextRange, end: number): void {
|
||||
Debug.assert(typeof end === "number");
|
||||
(<any>n)["__end"] = end;
|
||||
(<any>n).__end = end;
|
||||
}
|
||||
|
||||
export interface ConfigurableStart {
|
||||
@@ -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,
|
||||
|
||||
+33
-4
@@ -145,10 +145,15 @@ namespace ts {
|
||||
isCancellationRequested(): boolean;
|
||||
}
|
||||
|
||||
export interface InstallPackageOptions {
|
||||
fileName: Path;
|
||||
packageName: string;
|
||||
}
|
||||
|
||||
//
|
||||
// Public interface of the host of a language service instance.
|
||||
//
|
||||
export interface LanguageServiceHost {
|
||||
export interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
getNewLine?(): string;
|
||||
getProjectVersion?(): string;
|
||||
@@ -187,7 +192,6 @@ namespace ts {
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
|
||||
/*
|
||||
* getDirectories is also required for full import and type reference completions. Without it defined, certain
|
||||
@@ -199,6 +203,9 @@ namespace ts {
|
||||
* Gets a set of custom transformers to use during emit.
|
||||
*/
|
||||
getCustomTransformers?(): CustomTransformers | undefined;
|
||||
|
||||
isKnownTypesPackageName?(name: string): boolean;
|
||||
installPackage?(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -230,7 +237,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;
|
||||
@@ -276,11 +284,11 @@ namespace ts {
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
|
||||
|
||||
getProgram(): Program;
|
||||
|
||||
@@ -295,6 +303,10 @@ namespace ts {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface ApplyCodeActionCommandResult {
|
||||
successMessage: string;
|
||||
}
|
||||
|
||||
export interface Classifications {
|
||||
spans: number[];
|
||||
endOfLineState: EndOfLineState;
|
||||
@@ -367,6 +379,20 @@ namespace ts {
|
||||
description: string;
|
||||
/** Text changes to apply to each file as part of the code action */
|
||||
changes: FileTextChanges[];
|
||||
/**
|
||||
* If the user accepts the code fix, the editor should send the action back in a `applyAction` request.
|
||||
* This allows the language service to have side effects (e.g. installing dependencies) upon a code fix.
|
||||
*/
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
|
||||
// Publicly, this type is just `{}`. Internally it is a union of all the actions we use.
|
||||
// See `commands?: {}[]` in protocol.ts
|
||||
export type CodeActionCommand = InstallPackageAction;
|
||||
|
||||
export interface InstallPackageAction {
|
||||
/* @internal */ type: "install package";
|
||||
/* @internal */ packageName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -420,6 +446,7 @@ namespace ts {
|
||||
edits: FileTextChanges[];
|
||||
renameFilename: string | undefined;
|
||||
renameLocation: number | undefined;
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
|
||||
export interface TextInsertion {
|
||||
@@ -674,6 +701,7 @@ namespace ts {
|
||||
* be used in that case
|
||||
*/
|
||||
replacementSpan?: TextSpan;
|
||||
hasAction?: true;
|
||||
}
|
||||
|
||||
export interface CompletionEntryDetails {
|
||||
@@ -683,6 +711,7 @@ namespace ts {
|
||||
displayParts: SymbolDisplayPart[];
|
||||
documentation: SymbolDisplayPart[];
|
||||
tags: JSDocTagInfo[];
|
||||
codeActions?: CodeAction[];
|
||||
}
|
||||
|
||||
export interface OutliningSpan {
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/* @internal */ // Don't expose that we use this
|
||||
// Based on lib.es6.d.ts
|
||||
interface PromiseConstructor {
|
||||
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
|
||||
reject(reason: any): Promise<never>;
|
||||
}
|
||||
/* @internal */
|
||||
declare var Promise: PromiseConstructor;
|
||||
|
||||
// These utilities are common to multiple language service features.
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
@@ -427,8 +436,11 @@ namespace ts {
|
||||
return start < end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes `candidate.start <= position` holds.
|
||||
*/
|
||||
export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
return position < candidate.end || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
|
||||
export function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
|
||||
@@ -1278,9 +1290,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 +1307,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;
|
||||
|
||||
Reference in new issue
Block a user