Merge branch 'AddDefinitionAndBoundSpan' of https://github.com/armanio123/TypeScript into AddDefinitionAndBoundSpan

This commit is contained in:
Armando Aguirre committed 2017-10-24 14:02:41 -07:00
commit c62f2880cb
313 files changed
+120671 -112683

No files matched your search

+1 -3
View File
@@ -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 }],
};
}
}
+1
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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;
+17 -10
View File
@@ -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);
}
}
}
+44 -21
View File
@@ -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);
+61
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -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);
+2 -3
View File
@@ -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);
}
}
+14 -25
View File
@@ -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;
}
}
}
+2
View File
@@ -1,2 +1,4 @@
/// <reference path="annotateWithTypeFromJSDoc.ts" />
/// <reference path="convertFunctionToEs6Class.ts" />
/// <reference path="extractSymbol.ts" />
/// <reference path="installTypesForPackage.ts" />
+25 -8
View File
@@ -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,
+7 -4
View File
@@ -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;
+16 -5
View File
@@ -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
View File
@@ -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 {
+18 -4
View File
@@ -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;