Merge branch 'master' into preserveTypeAliases

# Conflicts:
#	tests/baselines/reference/modularizeLibrary_UsingES5LibAndES6FeatureLibs.types
This commit is contained in:
Anders Hejlsberg committed 2021-01-04 07:33:33 -10:00
commit 4c9675cc44
95 files changed
+2228 -494

No files matched your search

+5 -3
View File
@@ -5933,7 +5933,7 @@ namespace ts {
return factory.createPropertySignature(
/*modifiers*/ undefined,
name,
t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)
);
}));
@@ -33470,7 +33470,7 @@ namespace ts {
if (isInJSFile(node)) {
const typeTag = getJSDocTypeTag(node);
if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) {
error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature);
error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature);
}
}
}
@@ -40222,7 +40222,9 @@ namespace ts {
if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) {
const message = node.initializer
? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions
: Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations;
: !node.type
? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations
: Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context;
return grammarErrorOnNode(node.exclamationToken, message);
}
+28
View File
@@ -6143,6 +6143,34 @@
"category": "Message",
"code": 95148
},
"Return type must be inferred from a function": {
"category": "Message",
"code": 95149
},
"Could not determine function return type": {
"category": "Message",
"code": 95150
},
"Could not convert to arrow function": {
"category": "Message",
"code": 95151
},
"Could not convert to named function": {
"category": "Message",
"code": 95152
},
"Could not convert to anonymous function": {
"category": "Message",
"code": 95153
},
"Can only convert string concatenation": {
"category": "Message",
"code": 95154
},
"Selection is not a valid statement or statements": {
"category": "Message",
"code": 95155
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
+1 -1
View File
@@ -2992,7 +2992,7 @@ namespace ts {
}
export function getEffectiveImplementsTypeNodes(node: ClassLikeDeclaration): undefined | readonly ExpressionWithTypeArguments[]{
if(isInJSFile(node)) {
if (isInJSFile(node)) {
return getJSDocImplementsTags(node).map(n => n.class);
}
else {
+12 -6
View File
@@ -3420,6 +3420,12 @@ namespace FourSlash {
}
}
public verifyRefactorKindsAvailable(kind: string, expected: string[], preferences = ts.emptyOptions) {
const refactors = this.getApplicableRefactorsAtSelection("invoked", kind, preferences);
const availableKinds = ts.flatMap(refactors, refactor => refactor.actions).map(action => action.kind);
assert.deepEqual(availableKinds.sort(), expected.sort(), `Expected kinds to be equal`);
}
public verifyRefactorsAvailable(names: readonly string[]): void {
assert.deepEqual(unique(this.getApplicableRefactorsAtSelection(), r => r.name), names);
}
@@ -3833,14 +3839,14 @@ namespace FourSlash {
test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved");
}
private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit") {
return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, ts.emptyOptions, triggerReason);
private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, preferences = ts.emptyOptions) {
return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, preferences, triggerReason, kind);
}
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit"): readonly ts.ApplicableRefactorInfo[] {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason); // eslint-disable-line no-in-operator
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string): readonly ts.ApplicableRefactorInfo[] {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind); // eslint-disable-line no-in-operator
}
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason): readonly ts.ApplicableRefactorInfo[] {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason) || ts.emptyArray;
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason, kind?: string): readonly ts.ApplicableRefactorInfo[] {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason, kind) || ts.emptyArray;
}
public configurePlugin(pluginName: string, configuration: any): void {
+4
View File
@@ -215,6 +215,10 @@ namespace FourSlashInterface {
this.state.verifyRefactorAvailable(this.negative, triggerReason, name, actionName);
}
public refactorKindAvailable(kind: string, expected: string[], preferences = ts.emptyOptions) {
this.state.verifyRefactorKindsAvailable(kind, expected, preferences);
}
public toggleLineComment(newFileContent: string) {
this.state.toggleLineComment(newFileContent);
}
+92 -4
View File
@@ -1,15 +1,103 @@
declare namespace Reflect {
/**
* Calls the function with the specified object as the this value
* and the elements of specified array as the arguments.
* @param target The function to call.
* @param thisArgument The object to be used as the this object.
* @param argumentsList An array of argument values to be passed to the function.
*/
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>, newTarget?: any): any;
/**
* Constructs the target with the elements of specified array as the arguments
* and the specified constructor as the `new.target` value.
* @param target The constructor to invoke.
* @param argumentsList An array of argument values to be passed to the constructor.
* @param newTarget The constructor to be used as the `new.target` object.
*/
function construct(target: Function, argumentsList: ArrayLike<any>, newTarget?: Function): any;
/**
* Adds a property to an object, or modifies attributes of an existing property.
* @param target Object on which to add or modify the property. This can be a native JavaScript object
* (that is, a user-defined object or a built in object) or a DOM object.
* @param propertyKey The property name.
* @param attributes Descriptor for the property. It can be for a data property or an accessor property.
*/
function defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
/**
* Removes a property from an object, equivalent to `delete target[propertyKey]`,
* except it won't throw if `target[propertyKey]` is non-configurable.
* @param target Object from which to remove the own property.
* @param propertyKey The property name.
*/
function deleteProperty(target: object, propertyKey: PropertyKey): boolean;
/**
* Gets the property of target, equivalent to `target[propertyKey]` when `receiver === target`.
* @param target Object that contains the property on itself or in its prototype chain.
* @param propertyKey The property name.
* @param receiver The reference to use as the `this` value in the getter function,
* if `target[propertyKey]` is an accessor property.
*/
function get(target: object, propertyKey: PropertyKey, receiver?: any): any;
/**
* Gets the own property descriptor of the specified object.
* An own property descriptor is one that is defined directly on the object and is not inherited from the object's prototype.
* @param target Object that contains the property.
* @param propertyKey The property name.
*/
function getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor | undefined;
function getPrototypeOf(target: object): object;
/**
* Returns the prototype of an object.
* @param target The object that references the prototype.
*/
function getPrototypeOf(target: object): object | null;
/**
* Equivalent to `propertyKey in target`.
* @param target Object that contains the property on itself or in its prototype chain.
* @param propertyKey Name of the property.
*/
function has(target: object, propertyKey: PropertyKey): boolean;
/**
* Returns a value that indicates whether new properties can be added to an object.
* @param target Object to test.
*/
function isExtensible(target: object): boolean;
function ownKeys(target: object): PropertyKey[];
/**
* Returns the string and symbol keys of the own properties of an object. The own properties of an object
* are those that are defined directly on that object, and are not inherited from the object's prototype.
* @param target Object that contains the own properties.
*/
function ownKeys(target: object): (string | symbol)[];
/**
* Prevents the addition of new properties to an object.
* @param target Object to make non-extensible.
* @return Whether the object has been made non-extensible.
*/
function preventExtensions(target: object): boolean;
/**
* Sets the property of target, equivalent to `target[propertyKey] = value` when `receiver === target`.
* @param target Object that contains the property on itself or in its prototype chain.
* @param propertyKey Name of the property.
* @param receiver The reference to use as the `this` value in the setter function,
* if `target[propertyKey]` is an accessor property.
*/
function set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
function setPrototypeOf(target: object, proto: any): boolean;
/**
* Sets the prototype of a specified object o to object proto or null.
* @param target The object to change its prototype.
* @param proto The value of the new prototype or null.
* @return Whether setting the prototype was successful.
*/
function setPrototypeOf(target: object, proto: object | null): boolean;
}
+236 -16
View File
@@ -54,6 +54,239 @@ declare namespace Intl {
*/
type RelativeTimeFormatStyle = "long" | "short" | "narrow";
/**
* Unicode Calendar Identifier
*
* [Reference](http://unicode.org/reports/tr35/#UnicodeCalendarIdentifier)
*
* [Source](https://github.com/unicode-org/cldr/blob/b805d0b/common/bcp47/calendar.xml)
*/
type Calendar =
// Thai Buddhist calendar
| "buddhist"
// Traditional Chinese calendar
| "chinese"
// Coptic calendar
| "coptic"
// Traditional Korean calendar
| "dangi"
// Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E)
| "ethiopic-amete-alem" | "ethioaa"
// Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.)
| "ethiopic"
// Gregorian calendar
| "gregorian"
// Gregorian calendar (alias)
| "gregory"
// Traditional Hebrew calendar
| "hebrew"
// Indian calendar
| "indian"
// Islamic calendar
| "islamic"
// Islamic calendar, Umm al-Qura
| "islamic-umalqura"
// Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch)
| "islamic-tbla"
// Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch)
| "islamic-civil"
| "islamicc" // Deprecated alias
// Islamic calendar, Saudi Arabia sighting
| "islamic-rgsa"
// ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules)
| "iso8601"
// Japanese Imperial calendar
| "japanese"
// Persian calendar
| "persian"
// Republic of China calendar
| "roc"
;
/**
* Unicode Number System Identifier
*
* [Reference](http://unicode.org/reports/tr35/#UnicodeNumberSystemIdentifier)
*
* [Source](https://github.com/unicode-org/cldr/blob/be7a953/common/bcp47/number.xml)
*/
type NumberingSystem =
// Adlam digits
| "adlm"
// Ahom digits
| "ahom"
// Arabic-Indic digits
| "arab"
// Extended Arabic-Indic digits
| "arabext"
// Armenian upper case numerals — algorithmic
| "armn"
// Armenian lower case numerals — algorithmic
| "armnlow"
// Balinese digits
| "bali"
// Bengali digits
| "beng"
// Bhaiksuki digits
| "bhks"
// Brahmi digits
| "brah"
// Chakma digits
| "cakm"
// Cham digits
| "cham"
// Cyrillic numerals — algorithmic
| "cyrl"
// Devanagari digits
| "deva"
// Dives Akuru digits
| "diak"
// Ethiopic numerals — algorithmic
| "ethi"
// Financial numerals — may be algorithmic
| "finance"
// Full width digits
| "fullwide"
// Georgian numerals — algorithmic
| "geor"
// Gunjala Gondi digits
| "gong"
// Masaram Gondi digits
| "gonm"
// Greek upper case numerals — algorithmic
| "grek"
// Greek lower case numerals — algorithmic
| "greklow"
// Gujarati digits
| "gujr"
// Gurmukhi digits
| "guru"
// Han-character day-of-month numbering for lunar/other traditional calendars"
| "hanidays"
// Positional decimal system using Chinese number ideographs as digits
| "hanidec"
// Simplified Chinese numerals — algorithmic
| "hans"
// Simplified Chinese financial numerals — algorithmic
| "hansfin"
// Traditional Chinese numerals — algorithmic
| "hant"
// Traditional Chinese financial numerals — algorithmic
| "hantfin"
// Hebrew numerals — algorithmic
| "hebr"
// Pahawh Hmong digits
| "hmng"
// Nyiakeng Puachue Hmong digits
| "hmnp"
// Javanese digits
| "java"
// Japanese numerals — algorithmic
| "jpan"
// Japanese financial numerals — algorithmic
| "jpanfin"
// Japanese first-year Gannen numbering for Japanese calendar
| "jpanyear"
// Kayah Li digits
| "kali"
// Khmer digits
| "khmr"
// Kannada digits
| "knda"
// Tai Tham Hora (secular) digits
| "lana"
// Tai Tham Tham (ecclesiastical) digits
| "lanatham"
// Lao digits
| "laoo"
// Latin digits
| "latn"
// Lepcha digits
| "lepc"
// Limbu digits
| "limb"
// Mathematical bold digits
| "mathbold"
// Mathematical double-struck digits
| "mathdbl"
// Mathematical monospace digits
| "mathmono"
// Mathematical sans-serif bold digits
| "mathsanb"
// Mathematical sans-serif digits
| "mathsans"
// Malayalam digits
| "mlym"
// Modi digits
| "modi"
// Mongolian digits
| "mong"
// Mro digits
| "mroo"
// Meetei Mayek digits
| "mtei"
// Myanmar digits
| "mymr"
// Myanmar Shan digits
| "mymrshan"
// Myanmar Tai Laing digits
| "mymrtlng"
// Native digits
| "native"
// Newa digits
| "newa"
// N'Ko digits
| "nkoo"
// Ol Chiki digits
| "olck"
// Oriya digits
| "orya"
// Osmanya digits
| "osma"
// Hanifi Rohingya digits
| "rohg"
// Roman upper case numerals — algorithmic
| "roman"
// Roman lowercase numerals — algorithmic
| "romanlow"
// Saurashtra digits
| "saur"
// Sharada digits
| "shrd"
// Khudawadi digits
| "sind"
// Sinhala Lith digits
| "sinh"
// Sora_Sompeng digits
| "sora"
// Sundanese digits
| "sund"
// Takri digits
| "takr"
// New Tai Lue digits
| "talu"
// Tamil numerals — algorithmic
| "taml"
// Modern Tamil decimal digits
| "tamldec"
// Telugu digits
| "telu"
// Thai digits
| "thai"
// Tirhuta digits
| "tirh"
// Tibetan digits
| "tibt"
// Traditional numerals — may be algorithmic
| "traditional" | "traditio"
// Vai digits
| "vaii"
// Warang Citi digits
| "wara"
// Wancho digits
| "wcho"
;
/**
* An object with some or all of properties of `options` parameter
* of `Intl.RelativeTimeFormat` constructor.
@@ -281,23 +514,10 @@ declare namespace Intl {
interface DateTimeFormatOptions {
dateStyle?: "full" | "long" | "medium" | "short";
timeStyle?: "full" | "long" | "medium" | "short";
calendar?: "buddhist" | "chinese" | " coptic" | "ethiopia" | "ethiopic" | "gregory" | " hebrew" | "indian" | "islamic" | "iso8601" | " japanese" | "persian" | "roc";
dayPeriod?: "narrow" | "short" | " long";
numberingSystem?: "arab" | "arabext" | " bali" | "beng" | "deva" | "fullwide" | " gujr" | "guru" | "hanidec" | "khmr" | " knda" | "laoo" | "latn" | "limb" | "mlym" | " mong" | "mymr" | "orya" | "tamldec" | " telu" | "thai" | "tibt";
localeMatcher?: "best fit" | "lookup";
timeZone?: string;
hour12?: boolean;
calendar?: Calendar;
dayPeriod?: "narrow" | "short" | "long";
numberingSystem?: NumberingSystem;
hourCycle?: "h11" | "h12" | "h23" | "h24";
formatMatcher?: "best fit" | "basic";
weekday?: "long" | "short" | "narrow";
era?: "long" | "short" | "narrow";
year?: "numeric" | "2-digit";
month?: "numeric" | "2-digit" | "long" | "short" | "narrow";
day?: "numeric" | "2-digit";
hour?: "numeric" | "2-digit";
minute?: "numeric" | "2-digit";
second?: "numeric" | "2-digit";
fractionalSecondDigits?: 0 | 1 | 2 | 3;
timeZoneName?: "long" | "short";
}
}
+40 -28
View File
@@ -1199,7 +1199,7 @@ interface ConcatArray<T> {
interface Array<T> {
/**
* Gets or sets the length of the array. This is a number one higher than the highest element defined in an array.
* Gets or sets the length of the array. This is a number one higher than the highest index in the array.
*/
length: number;
/**
@@ -1212,44 +1212,54 @@ interface Array<T> {
toLocaleString(): string;
/**
* Removes the last element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
pop(): T | undefined;
/**
* Appends new elements to an array, and returns the new length of the array.
* @param items New elements of the Array.
* Appends new elements to the end of an array, and returns the new length of the array.
* @param items New elements to add to the array.
*/
push(...items: T[]): number;
/**
* Combines two or more arrays.
* @param items Additional items to add to the end of array1.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
concat(...items: ConcatArray<T>[]): T[];
/**
* Combines two or more arrays.
* @param items Additional items to add to the end of array1.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
concat(...items: (T | ConcatArray<T>)[]): T[];
/**
* Adds all the elements of an array separated by the specified separator string.
* @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma.
* Adds all the elements of an array into a string, separated by the specified separator string.
* @param separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
*/
join(separator?: string): string;
/**
* Reverses the elements in an Array.
* Reverses the elements in an array in place.
* This method mutates the array and returns a reference to the same array.
*/
reverse(): T[];
/**
* Removes the first element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
shift(): T | undefined;
/**
* Returns a section of an array.
* @param start The beginning of the specified portion of the array.
* @param end The end of the specified portion of the array. This is exclusive of the element at the index 'end'.
* Returns a copy of a section of an array.
* For both start and end, a negative index can be used to indicate an offset from the end of the array.
* For example, -2 refers to the second to last element of the array.
* @param start The beginning index of the specified portion of the array.
* If start is undefined, then the slice begins at index 0.
* @param end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'.
* If end is undefined, then the slice extends to the end of the array.
*/
slice(start?: number, end?: number): T[];
/**
* Sorts an array.
* Sorts an array in place.
* This method mutates the array and returns a reference to the same array.
* @param compareFn Function used to determine the order of the elements. It is expected to return
* a negative value if first argument is less than second argument, zero if they're equal and a positive
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
@@ -1262,6 +1272,7 @@ interface Array<T> {
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @returns An array containing the elements that were deleted.
*/
splice(start: number, deleteCount?: number): T[];
/**
@@ -1269,23 +1280,24 @@ interface Array<T> {
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @param items Elements to insert into the array in place of the deleted elements.
* @returns An array containing the elements that were deleted.
*/
splice(start: number, deleteCount: number, ...items: T[]): T[];
/**
* Inserts new elements at the start of an array.
* @param items Elements to insert at the start of the Array.
* Inserts new elements at the start of an array, and returns the new length of the array.
* @param items Elements to insert at the start of the array.
*/
unshift(...items: T[]): number;
/**
* Returns the index of the first occurrence of a value in an array.
* Returns the index of the first occurrence of a value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
*/
indexOf(searchElement: T, fromIndex?: number): number;
/**
* Returns the index of the last occurrence of a specified value in an array.
* Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at the last index in the array.
* @param fromIndex The array index at which to begin searching backward. If fromIndex is omitted, the search starts at the last index in the array.
*/
lastIndexOf(searchElement: T, fromIndex?: number): number;
/**
@@ -4318,17 +4330,17 @@ declare namespace Intl {
};
interface DateTimeFormatOptions {
localeMatcher?: string;
weekday?: string;
era?: string;
year?: string;
month?: string;
day?: string;
hour?: string;
minute?: string;
second?: string;
timeZoneName?: string;
formatMatcher?: string;
localeMatcher?: "best fit" | "lookup";
weekday?: "long" | "short" | "narrow";
era?: "long" | "short" | "narrow";
year?: "numeric" | "2-digit";
month?: "numeric" | "2-digit" | "long" | "short" | "narrow";
day?: "numeric" | "2-digit";
hour?: "numeric" | "2-digit";
minute?: "numeric" | "2-digit";
second?: "numeric" | "2-digit";
timeZoneName?: "long" | "short";
formatMatcher?: "best fit" | "basic";
hour12?: boolean;
timeZone?: string;
}
+7 -1
View File
@@ -566,7 +566,8 @@ namespace ts.server.protocol {
arguments: GetApplicableRefactorsRequestArgs;
}
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & {
triggerReason?: RefactorTriggerReason
triggerReason?: RefactorTriggerReason;
kind?: string;
};
export type RefactorTriggerReason = "implicit" | "invoked";
@@ -626,6 +627,11 @@ namespace ts.server.protocol {
* the current context.
*/
notApplicableReason?: string;
/**
* The hierarchical dotted name of the refactor action.
*/
kind?: string;
}
export interface GetEditsForRefactorRequest extends Request {
+1 -1
View File
@@ -2129,7 +2129,7 @@ namespace ts.server {
private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason);
return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind);
}
private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo {
@@ -235,7 +235,6 @@ namespace ts.codefix {
return transformThen(node, transformer, prevArgName);
}
if (isPromiseReturningCallExpression(node, transformer.checker, "catch")) {
if (node.arguments.length === 0) return silentFail();
return transformCatch(node, transformer, prevArgName);
}
if (isPropertyAccessExpression(node)) {
@@ -252,15 +251,13 @@ namespace ts.codefix {
}
function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] {
const func = node.arguments[0];
const argName = getArgBindingName(func, transformer);
const func = singleOrUndefined(node.arguments);
const argName = func ? getArgBindingName(func, transformer) : undefined;
let possibleNameForVarDecl: SynthIdentifier | undefined;
/*
If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block)
To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap
We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step
*/
// If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block)
// To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap
// We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step
if (prevArgName && !shouldReturn(node, transformer)) {
if (isSynthIdentifier(prevArgName)) {
possibleNameForVarDecl = prevArgName;
@@ -282,14 +279,12 @@ namespace ts.codefix {
}
const tryBlock = factory.createBlock(transformExpression(node.expression, transformer, possibleNameForVarDecl));
const transformationBody = getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer);
const transformationBody = func ? getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer) : emptyArray;
const catchArg = argName ? isSynthIdentifier(argName) ? argName.identifier.text : argName.bindingPattern : "e";
const catchVariableDeclaration = factory.createVariableDeclaration(catchArg);
const catchClause = factory.createCatchClause(catchVariableDeclaration, factory.createBlock(transformationBody));
/*
In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block)
*/
// In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block)
let varDeclList: VariableStatement | undefined;
let varDeclIdentifier: Identifier | undefined;
if (possibleNameForVarDecl && !shouldReturn(node, transformer)) {
@@ -52,6 +52,6 @@ namespace ts.codefix {
function getTypesPackageNameToInstall(packageName: string, host: LanguageServiceHost, diagCode: number): string | undefined {
return diagCode === errorCodeCannotFindModule
? (JsTyping.nodeCoreModules.has(packageName) ? "@types/node" : undefined)
: (host.isKnownTypesPackageName!(packageName) ? getTypesPackageName(packageName) : undefined); // TODO: GH#18217
: (host.isKnownTypesPackageName?.(packageName) ? getTypesPackageName(packageName) : undefined);
}
}
+14 -23
View File
@@ -4,7 +4,8 @@ namespace ts.codefix {
type AcceptedNameType = Identifier | StringLiteral;
type ContainerDeclaration = ClassLikeDeclaration | ObjectLiteralExpression;
interface Info {
type Info = AccessorInfo | refactor.RefactorErrorInfo;
interface AccessorInfo {
readonly container: ContainerDeclaration;
readonly isStatic: boolean;
readonly isReadonly: boolean;
@@ -16,20 +17,12 @@ namespace ts.codefix {
readonly renameAccessor: boolean;
}
type InfoOrError = {
info: Info,
error?: never
} | {
info?: never,
error: string
};
export function generateAccessorFromProperty(file: SourceFile, program: Program, start: number, end: number, context: textChanges.TextChangesContext, _actionName: string): FileTextChanges[] | undefined {
const fieldInfo = getAccessorConvertiblePropertyAtPosition(file, program, start, end);
if (!fieldInfo || !fieldInfo.info) return undefined;
if (!fieldInfo || refactor.isRefactorErrorInfo(fieldInfo)) return undefined;
const changeTracker = textChanges.ChangeTracker.fromContext(context);
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration } = fieldInfo.info;
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration } = fieldInfo;
suppressLeadingAndTrailingTrivia(fieldName);
suppressLeadingAndTrailingTrivia(accessorName);
@@ -112,7 +105,7 @@ namespace ts.codefix {
return modifierFlags;
}
export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, program: Program, start: number, end: number, considerEmptySpans = true): InfoOrError | undefined {
export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, program: Program, start: number, end: number, considerEmptySpans = true): Info | undefined {
const node = getTokenAtPosition(file, start);
const cursorRequest = start === end && considerEmptySpans;
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
@@ -142,17 +135,15 @@ namespace ts.codefix {
const fieldName = createPropertyName(startWithUnderscore ? name : getUniqueName(`_${name}`, file), declaration.name);
const accessorName = createPropertyName(startWithUnderscore ? getUniqueName(name.substring(1), file) : name, declaration.name);
return {
info: {
isStatic: hasStaticModifier(declaration),
isReadonly: hasEffectiveReadonlyModifier(declaration),
type: getDeclarationType(declaration, program),
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
originalName: (<AcceptedNameType>declaration.name).text,
declaration,
fieldName,
accessorName,
renameAccessor: startWithUnderscore
}
isStatic: hasStaticModifier(declaration),
isReadonly: hasEffectiveReadonlyModifier(declaration),
type: getDeclarationType(declaration, program),
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
originalName: (<AcceptedNameType>declaration.name).text,
declaration,
fieldName,
accessorName,
renameAccessor: startWithUnderscore
};
}
+3 -1
View File
@@ -11,7 +11,9 @@ namespace ts.refactor {
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] {
return arrayFrom(flatMapIterator(refactors.values(), refactor =>
context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context)));
context.cancellationToken && context.cancellationToken.isCancellationRequested() ||
!refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined :
refactor.getAvailableActions(context)));
}
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
@@ -2,45 +2,40 @@
namespace ts.refactor.addOrRemoveBracesToArrowFunction {
const refactorName = "Add or remove braces in an arrow function";
const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message;
const addBracesActionName = "Add braces to arrow function";
const removeBracesActionName = "Remove braces from arrow function";
const addBracesActionDescription = Diagnostics.Add_braces_to_arrow_function.message;
const removeBracesActionDescription = Diagnostics.Remove_braces_from_arrow_function.message;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
interface Info {
const addBracesAction = {
name: "Add braces to arrow function",
description: Diagnostics.Add_braces_to_arrow_function.message,
kind: "refactor.rewrite.arrow.braces.add",
};
const removeBracesAction = {
name: "Remove braces from arrow function",
description: Diagnostics.Remove_braces_from_arrow_function.message,
kind: "refactor.rewrite.arrow.braces.remove"
};
registerRefactor(refactorName, {
kinds: [removeBracesAction.kind],
getEditsForAction,
getAvailableActions });
interface FunctionBracesInfo {
func: ArrowFunction;
expression: Expression | undefined;
returnStatement?: ReturnStatement;
addBraces: boolean;
}
type InfoOrError = {
info: Info,
error?: never
} | {
info?: never,
error: string
};
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const { file, startPosition, triggerReason } = context;
const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked");
if (!info) return emptyArray;
if (info.error === undefined) {
if (!isRefactorErrorInfo(info)) {
return [{
name: refactorName,
description: refactorDescription,
actions: [
info.info.addBraces ?
{
name: addBracesActionName,
description: addBracesActionDescription
} : {
name: removeBracesActionName,
description: removeBracesActionDescription
}
info.addBraces ? addBracesAction : removeBracesAction
]
}];
}
@@ -49,15 +44,10 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
return [{
name: refactorName,
description: refactorDescription,
actions: [{
name: addBracesActionName,
description: addBracesActionDescription,
notApplicableReason: info.error
}, {
name: removeBracesActionName,
description: removeBracesActionDescription,
notApplicableReason: info.error
}]
actions: [
{ ...addBracesAction, notApplicableReason: info.error },
{ ...removeBracesAction, notApplicableReason: info.error },
]
}];
}
@@ -67,19 +57,19 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
const { file, startPosition } = context;
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
if (!info || !info.info) return undefined;
Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info");
const { expression, returnStatement, func } = info.info;
const { expression, returnStatement, func } = info;
let body: ConciseBody;
if (actionName === addBracesActionName) {
if (actionName === addBracesAction.name) {
const returnStatement = factory.createReturnStatement(expression);
body = factory.createBlock([returnStatement], /* multiLine */ true);
suppressLeadingAndTrailingTrivia(body);
copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
}
else if (actionName === removeBracesActionName && returnStatement) {
else if (actionName === removeBracesAction.name && returnStatement) {
const actualExpression = expression || factory.createVoidZero();
body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression;
suppressLeadingAndTrailingTrivia(body);
@@ -98,7 +88,7 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
return { renameFilename: undefined, renameLocation: undefined, edits };
}
function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true): InfoOrError | undefined {
function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined {
const node = getTokenAtPosition(file, startPosition);
const func = getContainingFunction(node);
@@ -118,26 +108,13 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
return undefined;
}
if (isExpression(func.body)) {
return {
info: {
func,
addBraces: true,
expression: func.body
}
};
if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) {
return { func, addBraces: true, expression: func.body };
}
else if (func.body.statements.length === 1) {
else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) {
const firstStatement = first(func.body.statements);
if (isReturnStatement(firstStatement)) {
return {
info: {
func,
addBraces: false,
expression: firstStatement.expression,
returnStatement: firstStatement
}
};
return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement };
}
}
return undefined;
@@ -3,15 +3,30 @@ namespace ts.refactor.convertArrowFunctionOrFunctionExpression {
const refactorName = "Convert arrow function or function expression";
const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression);
const toAnonymousFunctionActionName = "Convert to anonymous function";
const toNamedFunctionActionName = "Convert to named function";
const toArrowFunctionActionName = "Convert to arrow function";
const toAnonymousFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function);
const toNamedFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_named_function);
const toArrowFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function);
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
const toAnonymousFunctionAction = {
name: "Convert to anonymous function",
description: getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function),
kind: "refactor.rewrite.function.anonymous",
};
const toNamedFunctionAction = {
name: "Convert to named function",
description: getLocaleSpecificMessage(Diagnostics.Convert_to_named_function),
kind: "refactor.rewrite.function.named",
};
const toArrowFunctionAction = {
name: "Convert to arrow function",
description: getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function),
kind: "refactor.rewrite.function.arrow",
};
registerRefactor(refactorName, {
kinds: [
toAnonymousFunctionAction.kind,
toNamedFunctionAction.kind,
toArrowFunctionAction.kind
],
getEditsForAction,
getAvailableActions
});
interface FunctionInfo {
readonly selectedVariableDeclaration: boolean;
@@ -26,38 +41,50 @@ namespace ts.refactor.convertArrowFunctionOrFunctionExpression {
}
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const { file, startPosition, program } = context;
const { file, startPosition, program, kind } = context;
const info = getFunctionInfo(file, startPosition, program);
if (!info) return emptyArray;
const { selectedVariableDeclaration, func } = info;
const possibleActions: RefactorActionInfo[] = [];
if (selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent))) {
possibleActions.push({
name: toNamedFunctionActionName,
description: toNamedFunctionActionDescription
});
const errors: RefactorActionInfo[] = [];
if (refactorKindBeginsWith(toNamedFunctionAction.kind, kind)) {
const error = selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent)) ?
undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_named_function);
if (error) {
errors.push({ ...toNamedFunctionAction, notApplicableReason: error });
}
else {
possibleActions.push(toNamedFunctionAction);
}
}
if (!selectedVariableDeclaration && isArrowFunction(func)) {
possibleActions.push({
name: toAnonymousFunctionActionName,
description: toAnonymousFunctionActionDescription
});
if (refactorKindBeginsWith(toAnonymousFunctionAction.kind, kind)) {
const error = !selectedVariableDeclaration && isArrowFunction(func) ?
undefined: getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_anonymous_function);
if (error) {
errors.push({ ...toAnonymousFunctionAction, notApplicableReason: error });
}
else {
possibleActions.push(toAnonymousFunctionAction);
}
}
if (isFunctionExpression(func)) {
possibleActions.push({
name: toArrowFunctionActionName,
description: toArrowFunctionActionDescription
});
if (refactorKindBeginsWith(toArrowFunctionAction.kind, kind)) {
const error = isFunctionExpression(func) ? undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_arrow_function);
if (error) {
errors.push({ ...toArrowFunctionAction, notApplicableReason: error });
}
else {
possibleActions.push(toArrowFunctionAction);
}
}
return [{
name: refactorName,
description: refactorDescription,
actions: possibleActions
actions: possibleActions.length === 0 && context.preferences.provideRefactorNotApplicableReason ?
errors : possibleActions
}];
}
@@ -70,18 +97,18 @@ namespace ts.refactor.convertArrowFunctionOrFunctionExpression {
const edits: FileTextChanges[] = [];
switch (actionName) {
case toAnonymousFunctionActionName:
case toAnonymousFunctionAction.name:
edits.push(...getEditInfoForConvertToAnonymousFunction(context, func));
break;
case toNamedFunctionActionName:
case toNamedFunctionAction.name:
const variableInfo = getVariableInfo(func);
if (!variableInfo) return undefined;
edits.push(...getEditInfoForConvertToNamedFunction(context, func, variableInfo));
break;
case toArrowFunctionActionName:
case toArrowFunctionAction.name:
if (!isFunctionExpression(func)) return undefined;
edits.push(...getEditInfoForConvertToArrowFunction(context, func));
break;
+33 -25
View File
@@ -1,54 +1,62 @@
/* @internal */
namespace ts.refactor {
const refactorName = "Convert export";
const actionNameDefaultToNamed = "Convert default export to named export";
const actionNameNamedToDefault = "Convert named export to default export";
const defaultToNamedAction = {
name: "Convert default export to named export",
description: Diagnostics.Convert_default_export_to_named_export.message,
kind: "refactor.rewrite.export.named"
};
const namedToDefaultAction = {
name: "Convert named export to default export",
description: Diagnostics.Convert_named_export_to_default_export.message,
kind: "refactor.rewrite.export.default"
};
registerRefactor(refactorName, {
kinds: [
defaultToNamedAction.kind,
namedToDefaultAction.kind
],
getAvailableActions(context): readonly ApplicableRefactorInfo[] {
const info = getInfo(context, context.triggerReason === "invoked");
if (!info) return emptyArray;
if (info.error === undefined) {
const description = info.info.wasDefault ? Diagnostics.Convert_default_export_to_named_export.message : Diagnostics.Convert_named_export_to_default_export.message;
const actionName = info.info.wasDefault ? actionNameDefaultToNamed : actionNameNamedToDefault;
return [{ name: refactorName, description, actions: [{ name: actionName, description }] }];
if (!isRefactorErrorInfo(info)) {
const action = info.wasDefault ? defaultToNamedAction : namedToDefaultAction;
return [{ name: refactorName, description: action.description, actions: [action] }];
}
if (context.preferences.provideRefactorNotApplicableReason) {
return [
{ name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [{ name: actionNameDefaultToNamed, description: Diagnostics.Convert_default_export_to_named_export.message, notApplicableReason: info.error }] },
{ name: refactorName, description: Diagnostics.Convert_named_export_to_default_export.message, actions: [{ name: actionNameNamedToDefault, description: Diagnostics.Convert_named_export_to_default_export.message, notApplicableReason: info.error }] },
{ name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [
{ ...defaultToNamedAction, notApplicableReason: info.error },
{ ...namedToDefaultAction, notApplicableReason: info.error },
]}
];
}
return emptyArray;
},
getEditsForAction(context, actionName): RefactorEditInfo {
Debug.assert(actionName === actionNameDefaultToNamed || actionName === actionNameNamedToDefault, "Unexpected action name");
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, Debug.checkDefined(getInfo(context)?.info, "context must have info"), t, context.cancellationToken));
Debug.assert(actionName === defaultToNamedAction.name || actionName === namedToDefaultAction.name, "Unexpected action name");
const info = getInfo(context);
Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info");
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, info, t, context.cancellationToken));
return { edits, renameFilename: undefined, renameLocation: undefined };
},
});
// If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name.
type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement;
interface Info {
interface ExportInfo {
readonly exportNode: ExportToConvert;
readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s.
readonly wasDefault: boolean;
readonly exportingModuleSymbol: Symbol;
}
type InfoOrError = {
info: Info,
error?: never
} | {
info?: never,
error: string
};
function getInfo(context: RefactorContext, considerPartialSpans = true): InfoOrError | undefined {
function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined {
const { file } = context;
const span = getRefactorContextSpan(context);
const token = getTokenAtPosition(file, span.start);
@@ -74,7 +82,7 @@ namespace ts.refactor {
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ModuleDeclaration: {
const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration;
return node.name && isIdentifier(node.name) ? { info: { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } } : undefined;
return node.name && isIdentifier(node.name) ? { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } : undefined;
}
case SyntaxKind.VariableStatement: {
const vs = exportNode as VariableStatement;
@@ -85,19 +93,19 @@ namespace ts.refactor {
const decl = first(vs.declarationList.declarations);
if (!decl.initializer) return undefined;
Debug.assert(!wasDefault, "Can't have a default flag here");
return isIdentifier(decl.name) ? { info: { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } } : undefined;
return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined;
}
default:
return undefined;
}
}
function doChange(exportingSourceFile: SourceFile, program: Program, info: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void {
function doChange(exportingSourceFile: SourceFile, program: Program, info: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void {
changeExport(exportingSourceFile, info, changes, program.getTypeChecker());
changeImports(program, info, changes, cancellationToken);
}
function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: Info, changes: textChanges.ChangeTracker, checker: TypeChecker): void {
function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void {
if (wasDefault) {
changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list"));
}
@@ -131,7 +139,7 @@ namespace ts.refactor {
}
}
function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void {
function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void {
const checker = program.getTypeChecker();
const exportSymbol = Debug.checkDefined(checker.getSymbolAtLocation(exportName), "Export name should resolve to a symbol");
FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => {
+29 -20
View File
@@ -1,46 +1,55 @@
/* @internal */
namespace ts.refactor {
const refactorName = "Convert import";
const actionNameNamespaceToNamed = "Convert namespace import to named imports";
const actionNameNamedToNamespace = "Convert named imports to namespace import";
type NamedImportBindingsOrError = {
info: NamedImportBindings,
error?: never
} | {
info?: never,
error: string
const namespaceToNamedAction = {
name: "Convert namespace import to named imports",
description: Diagnostics.Convert_namespace_import_to_named_imports.message,
kind: "refactor.rewrite.import.named",
};
const namedToNamespaceAction = {
name: "Convert named imports to namespace import",
description: Diagnostics.Convert_named_imports_to_namespace_import.message,
kind: "refactor.rewrite.import.namespace",
};
registerRefactor(refactorName, {
kinds: [
namespaceToNamedAction.kind,
namedToNamespaceAction.kind
],
getAvailableActions(context): readonly ApplicableRefactorInfo[] {
const i = getImportToConvert(context, context.triggerReason === "invoked");
if (!i) return emptyArray;
const info = getImportToConvert(context, context.triggerReason === "invoked");
if (!info) return emptyArray;
if (i.error === undefined) {
const description = i.info.kind === SyntaxKind.NamespaceImport ? Diagnostics.Convert_namespace_import_to_named_imports.message : Diagnostics.Convert_named_imports_to_namespace_import.message;
const actionName = i.info.kind === SyntaxKind.NamespaceImport ? actionNameNamespaceToNamed : actionNameNamedToNamespace;
return [{ name: refactorName, description, actions: [{ name: actionName, description }] }];
if (!isRefactorErrorInfo(info)) {
const namespaceImport = info.kind === SyntaxKind.NamespaceImport;
const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction;
return [{ name: refactorName, description: action.description, actions: [action] }];
}
if (context.preferences.provideRefactorNotApplicableReason) {
return [
{ name: refactorName, description: Diagnostics.Convert_namespace_import_to_named_imports.message, actions: [{ name: actionNameNamespaceToNamed, description: Diagnostics.Convert_namespace_import_to_named_imports.message, notApplicableReason: i.error }] },
{ name: refactorName, description: Diagnostics.Convert_named_imports_to_namespace_import.message, actions: [{ name: actionNameNamedToNamespace, description: Diagnostics.Convert_named_imports_to_namespace_import.message, notApplicableReason: i.error }] }
{ name: refactorName, description: namespaceToNamedAction.description,
actions: [{ ...namespaceToNamedAction, notApplicableReason: info.error }] },
{ name: refactorName, description: namedToNamespaceAction.description,
actions: [{ ...namedToNamespaceAction, notApplicableReason: info.error }] }
];
}
return emptyArray;
},
getEditsForAction(context, actionName): RefactorEditInfo {
Debug.assert(actionName === actionNameNamespaceToNamed || actionName === actionNameNamedToNamespace, "Unexpected action name");
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, Debug.checkDefined(getImportToConvert(context)?.info, "Context must provide an import to convert")));
Debug.assert(actionName === namespaceToNamedAction.name || actionName === namedToNamespaceAction.name, "Unexpected action name");
const info = getImportToConvert(context);
Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info");
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info));
return { edits, renameFilename: undefined, renameLocation: undefined };
}
});
// Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`.
function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindingsOrError | undefined {
function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindings | RefactorErrorInfo | undefined {
const { file } = context;
const span = getRefactorContextSpan(context);
const token = getTokenAtPosition(file, span.start);
@@ -57,7 +66,7 @@ namespace ts.refactor {
return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) };
}
return { info: importClause.namedBindings };
return importClause.namedBindings;
}
function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImportBindings): void {
@@ -2,8 +2,17 @@
namespace ts.refactor.addOrRemoveBracesToArrowFunction {
const refactorName = "Convert overload list to single signature";
const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
const functionOverloadAction = {
name: refactorName,
description: refactorDescription,
kind: "refactor.rewrite.function.overloadList",
};
registerRefactor(refactorName, {
kinds: [functionOverloadAction.kind],
getEditsForAction,
getAvailableActions
});
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const { file, startPosition, program } = context;
@@ -13,10 +22,7 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
return [{
name: refactorName,
description: refactorDescription,
actions: [{
name: refactorName,
description: refactorDescription
}]
actions: [functionOverloadAction]
}];
}
@@ -2,8 +2,18 @@
namespace ts.refactor.convertParamsToDestructuredObject {
const refactorName = "Convert parameters to destructured object";
const minimumParameterLength = 2;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object);
const toDestructuredAction = {
name: refactorName,
description: refactorDescription,
kind: "refactor.rewrite.parameters.toDestructured"
};
registerRefactor(refactorName, {
kinds: [toDestructuredAction.kind],
getEditsForAction,
getAvailableActions
});
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const { file, startPosition } = context;
@@ -12,14 +22,10 @@ namespace ts.refactor.convertParamsToDestructuredObject {
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker());
if (!functionDeclaration) return emptyArray;
const description = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object);
return [{
name: refactorName,
description,
actions: [{
name: refactorName,
description
}]
description: refactorDescription,
actions: [toDestructuredAction]
}];
}
@@ -3,7 +3,16 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
const refactorName = "Convert to template string";
const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string);
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
const convertStringAction = {
name: refactorName,
description: refactorDescription,
kind: "refactor.rewrite.string"
};
registerRefactor(refactorName, {
kinds: [convertStringAction.kind],
getEditsForAction,
getAvailableActions
});
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const { file, startPosition } = context;
@@ -12,7 +21,13 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
const refactorInfo: ApplicableRefactorInfo = { name: refactorName, description: refactorDescription, actions: [] };
if (isBinaryExpression(maybeBinary) && isStringConcatenationValid(maybeBinary)) {
refactorInfo.actions.push({ name: refactorName, description: refactorDescription });
refactorInfo.actions.push(convertStringAction);
return [refactorInfo];
}
else if (context.preferences.provideRefactorNotApplicableReason) {
refactorInfo.actions.push({ ...convertStringAction,
notApplicableReason: getLocaleSpecificMessage(Diagnostics.Can_only_convert_string_concatenation)
});
return [refactorInfo];
}
return emptyArray;
@@ -3,20 +3,26 @@ namespace ts.refactor.convertToOptionalChainExpression {
const refactorName = "Convert to optional chain expression";
const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression);
registerRefactor(refactorName, { getAvailableActions, getEditsForAction });
const toOptionalChainAction = {
name: refactorName,
description: convertToOptionalChainExpressionMessage,
kind: "refactor.rewrite.expression.optionalChain",
};
registerRefactor(refactorName, {
kinds: [toOptionalChainAction.kind],
getAvailableActions,
getEditsForAction
});
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const info = getInfo(context, context.triggerReason === "invoked");
if (!info) return emptyArray;
if (!info.error) {
if (!isRefactorErrorInfo(info)) {
return [{
name: refactorName,
description: convertToOptionalChainExpressionMessage,
actions: [{
name: refactorName,
description: convertToOptionalChainExpressionMessage
}]
actions: [toOptionalChainAction],
}];
}
@@ -24,11 +30,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
return [{
name: refactorName,
description: convertToOptionalChainExpressionMessage,
actions: [{
name: refactorName,
description: convertToOptionalChainExpressionMessage,
notApplicableReason: info.error
}]
actions: [{ ...toOptionalChainAction, notApplicableReason: info.error }],
}];
}
return emptyArray;
@@ -36,24 +38,16 @@ namespace ts.refactor.convertToOptionalChainExpression {
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
const info = getInfo(context);
if (!info || !info.info) return undefined;
Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info");
const edits = textChanges.ChangeTracker.with(context, t =>
doChange(context.file, context.program.getTypeChecker(), t, Debug.checkDefined(info.info, "context must have info"), actionName)
doChange(context.file, context.program.getTypeChecker(), t, info, actionName)
);
return { edits, renameFilename: undefined, renameLocation: undefined };
}
type InfoOrError = {
info: Info,
error?: never;
} | {
info?: never,
error: string;
};
type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier;
interface Info {
interface OptionalChainInfo {
finalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression,
occurrences: Occurrence[],
expression: ValidExpression,
@@ -83,7 +77,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
return isValidExpression(node) || isValidStatement(node);
}
function getInfo(context: RefactorContext, considerEmptySpans = true): InfoOrError | undefined {
function getInfo(context: RefactorContext, considerEmptySpans = true): OptionalChainInfo | RefactorErrorInfo | undefined {
const { file, program } = context;
const span = getRefactorContextSpan(context);
@@ -103,7 +97,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
return isConditionalExpression(expression) ? getConditionalInfo(expression, checker) : getBinaryInfo(expression);
}
function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): InfoOrError | undefined {
function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): OptionalChainInfo | RefactorErrorInfo | undefined {
const condition = expression.condition;
const finalExpression = getFinalExpressionInChain(expression.whenTrue);
@@ -113,16 +107,16 @@ namespace ts.refactor.convertToOptionalChainExpression {
if ((isPropertyAccessExpression(condition) || isIdentifier(condition))
&& getMatchingStart(condition, finalExpression.expression)) {
return { info: { finalExpression, occurrences: [condition], expression } };
return { finalExpression, occurrences: [condition], expression };
}
else if (isBinaryExpression(condition)) {
const occurrences = getOccurrencesInExpression(finalExpression.expression, condition);
return occurrences ? { info: { finalExpression, occurrences, expression } } :
return occurrences ? { finalExpression, occurrences, expression } :
{ error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) };
}
}
function getBinaryInfo(expression: BinaryExpression): InfoOrError | undefined {
function getBinaryInfo(expression: BinaryExpression): OptionalChainInfo | RefactorErrorInfo | undefined {
if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken) {
return { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_logical_AND_access_chains) };
};
@@ -131,7 +125,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
if (!finalExpression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) };
const occurrences = getOccurrencesInExpression(finalExpression.expression, expression.left);
return occurrences ? { info: { finalExpression, occurrences, expression } } :
return occurrences ? { finalExpression, occurrences, expression } :
{ error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) };
}
@@ -288,7 +282,7 @@ namespace ts.refactor.convertToOptionalChainExpression {
return toConvert;
}
function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: Info, _actionName: string): void {
function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: OptionalChainInfo, _actionName: string): void {
const { finalExpression, occurrences, expression } = info;
const firstOccurrence = occurrences[occurrences.length - 1];
const convertedChain = convertOccurrences(checker, finalExpression, occurrences);
+79 -54
View File
@@ -1,39 +1,56 @@
/* @internal */
namespace ts.refactor.extractSymbol {
const refactorName = "Extract Symbol";
registerRefactor(refactorName, { getAvailableActions, getEditsForAction });
const extractConstantAction = {
name: "Extract Constant",
description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
kind: "refactor.extract.constant",
};
const extractFunctionAction = {
name: "Extract Function",
description: getLocaleSpecificMessage(Diagnostics.Extract_function),
kind: "refactor.extract.function",
};
registerRefactor(refactorName, {
kinds: [
extractConstantAction.kind,
extractFunctionAction.kind
],
getAvailableActions,
getEditsForAction
});
/**
* Compute the associated code actions
* Exported for tests.
*/
export function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const requestedRefactor = context.kind;
const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked");
const targetRange = rangeToExtract.targetRange;
if (targetRange === undefined) {
if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) {
return emptyArray;
}
return [{
name: refactorName,
description: getLocaleSpecificMessage(Diagnostics.Extract_function),
actions: [{
description: getLocaleSpecificMessage(Diagnostics.Extract_function),
name: "function_extract_error",
notApplicableReason: getStringError(rangeToExtract.errors)
}]
},
{
name: refactorName,
description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
actions: [{
description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
name: "constant_extract_error",
notApplicableReason: getStringError(rangeToExtract.errors)
}]
}];
const errors = [];
if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) {
errors.push({
name: refactorName,
description: extractFunctionAction.description,
actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }]
});
}
if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) {
errors.push({
name: refactorName,
description: extractConstantAction.description,
actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }]
});
}
return errors;
}
const extractions = getPossibleExtractions(targetRange, context);
@@ -53,46 +70,54 @@ namespace ts.refactor.extractSymbol {
let i = 0;
for (const { functionExtraction, constantExtraction } of extractions) {
const description = functionExtraction.description;
if (functionExtraction.errors.length === 0) {
// Don't issue refactorings with duplicated names.
// Scopes come back in "innermost first" order, so extractions will
// preferentially go into nearer scopes
if (!usedFunctionNames.has(description)) {
usedFunctionNames.set(description, true);
functionActions.push({
if(refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)){
if (functionExtraction.errors.length === 0) {
// Don't issue refactorings with duplicated names.
// Scopes come back in "innermost first" order, so extractions will
// preferentially go into nearer scopes
if (!usedFunctionNames.has(description)) {
usedFunctionNames.set(description, true);
functionActions.push({
description,
name: `function_scope_${i}`,
kind: extractFunctionAction.kind
});
}
}
else if (!innermostErrorFunctionAction) {
innermostErrorFunctionAction = {
description,
name: `function_scope_${i}`
});
name: `function_scope_${i}`,
notApplicableReason: getStringError(functionExtraction.errors),
kind: extractFunctionAction.kind
};
}
}
else if (!innermostErrorFunctionAction) {
innermostErrorFunctionAction = {
description,
name: `function_scope_${i}`,
notApplicableReason: getStringError(functionExtraction.errors)
};
}
// Skip these since we don't have a way to report errors yet
if (constantExtraction.errors.length === 0) {
// Don't issue refactorings with duplicated names.
// Scopes come back in "innermost first" order, so extractions will
// preferentially go into nearer scopes
const description = constantExtraction.description;
if (!usedConstantNames.has(description)) {
usedConstantNames.set(description, true);
constantActions.push({
if(refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) {
if (constantExtraction.errors.length === 0) {
// Don't issue refactorings with duplicated names.
// Scopes come back in "innermost first" order, so extractions will
// preferentially go into nearer scopes
const description = constantExtraction.description;
if (!usedConstantNames.has(description)) {
usedConstantNames.set(description, true);
constantActions.push({
description,
name: `constant_scope_${i}`,
kind: extractConstantAction.kind
});
}
}
else if (!innermostErrorConstantAction) {
innermostErrorConstantAction = {
description,
name: `constant_scope_${i}`
});
name: `constant_scope_${i}`,
notApplicableReason: getStringError(constantExtraction.errors),
kind: extractConstantAction.kind
};
}
}
else if (!innermostErrorConstantAction) {
innermostErrorConstantAction = {
description,
name: `constant_scope_${i}`,
notApplicableReason: getStringError(constantExtraction.errors)
};
}
// *do* increment i anyway because we'll look for the i-th scope
@@ -106,7 +131,7 @@ namespace ts.refactor.extractSymbol {
infos.push({
name: refactorName,
description: getLocaleSpecificMessage(Diagnostics.Extract_function),
actions: functionActions
actions: functionActions,
});
}
else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) {
+37 -29
View File
@@ -1,25 +1,39 @@
/* @internal */
namespace ts.refactor {
const refactorName = "Extract type";
const extractToTypeAlias = "Extract to type alias";
const extractToInterface = "Extract to interface";
const extractToTypeDef = "Extract to typedef";
const extractToTypeAliasAction = {
name: "Extract to type alias",
description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias),
kind: "refactor.extract.type",
};
const extractToInterfaceAction = {
name: "Extract to interface",
description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface),
kind: "refactor.extract.interface",
};
const extractToTypeDefAction = {
name: "Extract to typedef",
description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef),
kind: "refactor.extract.typedef"
};
registerRefactor(refactorName, {
kinds: [
extractToTypeAliasAction.kind,
extractToInterfaceAction.kind,
extractToTypeDefAction.kind
],
getAvailableActions(context): readonly ApplicableRefactorInfo[] {
const info = getRangeToExtract(context, context.triggerReason === "invoked");
if (!info) return emptyArray;
if (info.error === undefined) {
if (!isRefactorErrorInfo(info)) {
return [{
name: refactorName,
description: getLocaleSpecificMessage(Diagnostics.Extract_type),
actions: info.info.isJS ? [{
name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef)
}] : append([{
name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias)
}], info.info.typeElements && {
name: extractToInterface, description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface)
})
actions: info.isJS ?
[extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction)
}];
}
@@ -28,9 +42,9 @@ namespace ts.refactor {
name: refactorName,
description: getLocaleSpecificMessage(Diagnostics.Extract_type),
actions: [
{ name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), notApplicableReason: info.error },
{ name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), notApplicableReason: info.error },
{ name: extractToInterface, description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), notApplicableReason: info.error },
{ ...extractToTypeDefAction, notApplicableReason: info.error },
{ ...extractToTypeAliasAction, notApplicableReason: info.error },
{ ...extractToInterfaceAction, notApplicableReason: info.error },
]
}];
}
@@ -39,18 +53,19 @@ namespace ts.refactor {
},
getEditsForAction(context, actionName): RefactorEditInfo {
const { file, } = context;
const info = Debug.checkDefined(getRangeToExtract(context)?.info, "Expected to find a range to extract");
const info = getRangeToExtract(context);
Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract");
const name = getUniqueName("NewType", file);
const edits = textChanges.ChangeTracker.with(context, changes => {
switch (actionName) {
case extractToTypeAlias:
case extractToTypeAliasAction.name:
Debug.assert(!info.isJS, "Invalid actionName/JS combo");
return doTypeAliasChange(changes, file, name, info);
case extractToTypeDef:
case extractToTypeDefAction.name:
Debug.assert(info.isJS, "Invalid actionName/JS combo");
return doTypedefChange(changes, file, name, info);
case extractToInterface:
case extractToInterfaceAction.name:
Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo");
return doInterfaceChange(changes, file, name, info as InterfaceInfo);
default:
@@ -72,16 +87,9 @@ namespace ts.refactor {
isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[];
}
type Info = TypeAliasInfo | InterfaceInfo;
type InfoOrError = {
info: Info,
error?: never
} | {
info?: never,
error: string
};
type ExtractInfo = TypeAliasInfo | InterfaceInfo;
function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): InfoOrError | undefined {
function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined {
const { file, startPosition } = context;
const isJS = isSourceFileJS(file);
const current = getTokenAtPosition(file, startPosition);
@@ -98,7 +106,7 @@ namespace ts.refactor {
if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) };
const typeElements = flattenTypeLiteralNodeReference(checker, selection);
return { info: { isJS, selection, firstStatement, typeParameters, typeElements } };
return { isJS, selection, firstStatement, typeParameters, typeElements };
}
function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined {
@@ -209,7 +217,7 @@ namespace ts.refactor {
changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace });
}
function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) {
function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: ExtractInfo) {
const { firstStatement, selection, typeParameters } = info;
const node = factory.createJSDocTypedefTag(
@@ -2,18 +2,25 @@
namespace ts.refactor.generateGetAccessorAndSetAccessor {
const actionName = "Generate 'get' and 'set' accessors";
const actionDescription = Diagnostics.Generate_get_and_set_accessors.message;
const generateGetSetAction = {
name: actionName,
description: actionDescription,
kind: "refactor.rewrite.property.generateAccessors",
};
registerRefactor(actionName, {
kinds: [generateGetSetAction.kind],
getEditsForAction(context, actionName) {
if (!context.endPosition) return undefined;
const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition);
if (!info || !info.info) return undefined;
Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info");
const edits = codefix.generateAccessorFromProperty(context.file, context.program, context.startPosition, context.endPosition, context, actionName);
if (!edits) return undefined;
const renameFilename = context.file.fileName;
const nameNeedRename = info.info.renameAccessor ? info.info.accessorName : info.info.fieldName;
const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName;
const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1;
const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.info.declaration));
const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration));
return { renameFilename, renameLocation, edits };
},
@@ -22,16 +29,11 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition, context.triggerReason === "invoked");
if (!info) return emptyArray;
if (!info.error) {
if (!isRefactorErrorInfo(info)) {
return [{
name: actionName,
description: actionDescription,
actions: [
{
name: actionName,
description: actionDescription
}
]
actions: [generateGetSetAction],
}];
}
@@ -39,11 +41,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
return [{
name: actionName,
description: actionDescription,
actions: [{
name: actionName,
description: actionDescription,
notApplicableReason: info.error
}]
actions: [{ ...generateGetSetAction, notApplicableReason: info.error }],
}];
}
+25
View File
@@ -0,0 +1,25 @@
/* @internal */
namespace ts.refactor {
/**
* Returned by refactor funtions when some error message needs to be surfaced to users.
*/
export interface RefactorErrorInfo {
error: string;
};
/**
* Checks if some refactor info has refactor error info.
*/
export function isRefactorErrorInfo(info: unknown): info is RefactorErrorInfo {
return (info as RefactorErrorInfo).error !== undefined;
}
/**
* Checks if string "known" begins with string "requested".
* Used to match requested kinds with a known kind.
*/
export function refactorKindBeginsWith(known: string, requested: string | undefined): boolean {
if(!requested) return true;
return known.substr(0, requested.length) === requested;
}
}
@@ -2,11 +2,21 @@
namespace ts.refactor.inferFunctionReturnType {
const refactorName = "Infer function return type";
const refactorDescription = Diagnostics.Infer_function_return_type.message;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
const inferReturnTypeAction = {
name: refactorName,
description: refactorDescription,
kind: "refactor.rewrite.function.returnType"
};
registerRefactor(refactorName, {
kinds: [inferReturnTypeAction.kind],
getEditsForAction,
getAvailableActions
});
function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined {
const info = getInfo(context);
if (info) {
if (info && !isRefactorErrorInfo(info)) {
const edits = textChanges.ChangeTracker.with(context, t =>
t.tryInsertTypeAnnotation(context.file, info.declaration, info.returnTypeNode));
return { renameFilename: undefined, renameLocation: undefined, edits };
@@ -16,14 +26,19 @@ namespace ts.refactor.inferFunctionReturnType {
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] {
const info = getInfo(context);
if (info) {
if (!info) return emptyArray;
if (!isRefactorErrorInfo(info)) {
return [{
name: refactorName,
description: refactorDescription,
actions: [{
name: refactorName,
description: refactorDescription
}]
actions: [inferReturnTypeAction]
}];
}
if (context.preferences.provideRefactorNotApplicableReason) {
return [{
name: refactorName,
description: refactorDescription,
actions: [{ ...inferReturnTypeAction, notApplicableReason: info.error }]
}];
}
return emptyArray;
@@ -35,21 +50,25 @@ namespace ts.refactor.inferFunctionReturnType {
| ArrowFunction
| MethodDeclaration;
interface Info {
interface FunctionInfo {
declaration: ConvertibleDeclaration;
returnTypeNode: TypeNode;
}
function getInfo(context: RefactorContext): Info | undefined {
if (isInJSFile(context.file)) return;
function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined {
if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return;
const token = getTokenAtPosition(context.file, context.startPosition);
const declaration = findAncestor(token, isConvertibleDeclaration);
if (!declaration || !declaration.body || declaration.type) return;
if (!declaration || !declaration.body || declaration.type) {
return { error: getLocaleSpecificMessage(Diagnostics.Return_type_must_be_inferred_from_a_function) };
}
const typeChecker = context.program.getTypeChecker();
const returnType = tryGetReturnType(typeChecker, declaration);
if (!returnType) return;
if (!returnType) {
return { error: getLocaleSpecificMessage(Diagnostics.Could_not_determine_function_return_type) };
};
const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation);
if (returnTypeNode) {
+18 -3
View File
@@ -1,11 +1,26 @@
/* @internal */
namespace ts.refactor {
const refactorName = "Move to a new file";
const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file);
const moveToNewFileAction = {
name: refactorName,
description,
kind: "refactor.move.newFile",
};
registerRefactor(refactorName, {
kinds: [moveToNewFileAction.kind],
getAvailableActions(context): readonly ApplicableRefactorInfo[] {
if (!context.preferences.allowTextChangesInNewFiles || getStatementsToMove(context) === undefined) return emptyArray;
const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file);
return [{ name: refactorName, description, actions: [{ name: refactorName, description }] }];
const statements = getStatementsToMove(context);
if (context.preferences.allowTextChangesInNewFiles && statements) {
return [{ name: refactorName, description, actions: [moveToNewFileAction] }];
}
if (context.preferences.provideRefactorNotApplicableReason) {
return [{ name: refactorName, description, actions:
[{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }]
}];
}
return emptyArray;
},
getEditsForAction(context, actionName): RefactorEditInfo {
Debug.assert(actionName === refactorName, "Wrong refactor invoked");
+24 -21
View File
@@ -550,9 +550,8 @@ namespace ts {
getJsDocTags(): JSDocTagInfo[] {
if (this.jsDocTags === undefined) {
this.jsDocTags = this.declaration ? JsDoc.getJsDocTagsFromDeclarations([this.declaration]) : [];
this.jsDocTags = this.declaration ? getJsDocTags([this.declaration], this.checker) : [];
}
return this.jsDocTags;
}
}
@@ -566,13 +565,26 @@ namespace ts {
return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc");
}
function getJsDocTags(declarations: Declaration[], checker: TypeChecker): JSDocTagInfo[] {
let tags = JsDoc.getJsDocTagsFromDeclarations(declarations);
if (tags.length === 0 || declarations.some(hasJSDocInheritDocTag)) {
forEachUnique(declarations, declaration => {
const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => symbol.getJsDocTags());
if (inheritedTags) {
tags = [...inheritedTags, ...tags];
}
});
}
return tags;
}
function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] {
if (!declarations) return emptyArray;
let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations);
if (doc.length === 0 || declarations.some(hasJSDocInheritDocTag)) {
if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) {
forEachUnique(declarations, declaration => {
const inheritedDocs = findInheritedJSDocComments(declaration, declaration.symbol.name, checker!); // TODO: GH#18217
const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => symbol.getDocumentationComment(checker));
// TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs
if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc);
});
@@ -580,20 +592,10 @@ namespace ts {
return doc;
}
/**
* Attempts to find JSDoc comments for possibly-inherited properties. Checks superclasses then traverses
* implemented interfaces until a symbol is found with the same name and with documentation.
* @param declaration The possibly-inherited declaration to find comments for.
* @param propertyName The name of the possibly-inherited property.
* @param typeChecker A TypeChecker, used to find inherited properties.
* @returns A filled array of documentation comments if any were found, otherwise an empty array.
*/
function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): readonly SymbolDisplayPart[] | undefined {
function findBaseOfDeclaration<T>(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[]): T[] | undefined {
return firstDefined(declaration.parent ? getAllSuperTypeNodes(declaration.parent) : emptyArray, superTypeNode => {
const superType = typeChecker.getTypeAtLocation(superTypeNode);
const baseProperty = superType && typeChecker.getPropertyOfType(superType, propertyName);
const inheritedDocs = baseProperty && baseProperty.getDocumentationComment(typeChecker);
return inheritedDocs && inheritedDocs.length ? inheritedDocs : undefined;
const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(superTypeNode), declaration.symbol.name);
return symbol ? cb(symbol) : undefined;
});
}
@@ -2451,7 +2453,7 @@ namespace ts {
return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, options);
}
function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason): RefactorContext {
function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext {
const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end];
return {
file,
@@ -2463,6 +2465,7 @@ namespace ts {
cancellationToken,
preferences,
triggerReason,
kind
};
}
@@ -2470,10 +2473,10 @@ namespace ts {
return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName));
}
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason): ApplicableRefactorInfo[] {
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] {
synchronizeHostData();
const file = getValidSourceFile(fileName);
return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason));
return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind));
}
function getEditsForRefactor(
@@ -2694,7 +2697,7 @@ namespace ts {
return symbol ? [symbol] : emptyArray;
}
const discriminatedPropertySymbols = mapDefined(contextualType.types, t => isObjectLiteralExpression(node.parent) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name));
const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name));
if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) {
const symbol = contextualType.getProperty(name);
if (symbol) return [symbol];
+24 -13
View File
@@ -100,31 +100,42 @@ namespace ts.SymbolDisplay {
return ScriptElementKind.unknown;
}
function isDeprecatedDeclaration(decl: Declaration) {
return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated);
}
function getNormalizedSymbolModifiers(symbol: Symbol) {
if (symbol.declarations && symbol.declarations.length) {
const [declaration, ...declarations] = symbol.declarations;
// omit deprecated flag if some declarations are not deprecated
const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d))
? ModifierFlags.Deprecated
: ModifierFlags.None;
const modifiers = getNodeModifiers(declaration, excludeFlags);
if (modifiers) {
return modifiers.split(",");
}
}
return [];
}
export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string {
if (!symbol) {
return ScriptElementKindModifier.none;
}
const modifiers = new Set<string>();
if (symbol.declarations && symbol.declarations.length > 0) {
const kindModifiers = getNodeModifiers(symbol.declarations[0]);
if (kindModifiers !== ScriptElementKindModifier.none) {
kindModifiers.split(",").forEach(m => modifiers.add(m));
}
}
const modifiers = new Set(getNormalizedSymbolModifiers(symbol));
if (symbol.flags & SymbolFlags.Alias) {
const resolvedSymbol = typeChecker.getAliasedSymbol(symbol);
if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) {
const kindModifiers = getNodeModifiers(resolvedSymbol.declarations[0]);
if (kindModifiers !== ScriptElementKindModifier.none) {
kindModifiers.split(",").forEach(m => modifiers.add(m));
}
if (resolvedSymbol !== symbol) {
forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => {
modifiers.add(modifier);
});
}
}
if (symbol.flags & SymbolFlags.Optional) {
modifiers.add(ScriptElementKindModifier.optionalModifier);
}
return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none;
}
+1
View File
@@ -116,6 +116,7 @@
"refactors/extractSymbol.ts",
"refactors/extractType.ts",
"refactors/generateGetAccessorAndSetAccessor.ts",
"refactors/helpers.ts",
"refactors/moveToNewFile.ts",
"refactors/addOrRemoveBracesToArrowFunction.ts",
"refactors/convertParamsToDestructuredObject.ts",
+11 -1
View File
@@ -520,7 +520,7 @@ namespace ts {
/** @deprecated `fileName` will be ignored */
applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason): ApplicableRefactorInfo[];
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined;
organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[];
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[];
@@ -792,6 +792,11 @@ namespace ts {
* the current context.
*/
notApplicableReason?: string;
/**
* The hierarchical dotted name of the refactor action.
*/
kind?: string;
}
/**
@@ -1464,6 +1469,10 @@ namespace ts {
/** @internal */
export interface Refactor {
/** List of action kinds a refactor can provide.
* Used to skip unnecessary calculation when specific refactors are requested. */
kinds?: string[];
/** Compute the associated code actions */
getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined;
@@ -1480,5 +1489,6 @@ namespace ts {
cancellationToken?: CancellationToken;
preferences: UserPreferences;
triggerReason?: RefactorTriggerReason;
kind?: string;
}
}
+4 -2
View File
@@ -1588,9 +1588,11 @@ namespace ts {
return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0;
}
export function getNodeModifiers(node: Node): string {
const flags = isDeclaration(node) ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) : ModifierFlags.None;
export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string {
const result: string[] = [];
const flags = isDeclaration(node)
? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags
: ModifierFlags.None;
if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier);
if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier);
@@ -1577,6 +1577,18 @@ const fn = (): Promise<(message: string) => void> =>
function [#|f|]() {
return fn().then(res => res("test"));
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch1", `
function [#|f|]() {
return Promise.resolve().catch();
}
`);
_testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch2", `
function [#|f|]() {
return Promise.resolve(0).then(x => x).catch();
}
`);
_testConvertToAsyncFunctionWithModule("convertToAsyncFunction_importedFunction", `