Merge branch 'master' into incrementalBuildInfo

This commit is contained in:
Sheetal Nandi
2019-03-08 11:42:19 -08:00
117 changed files with 4728 additions and 5649 deletions

View File

@@ -35,7 +35,7 @@ namespace ts.codefix {
precedingNode = ctorDeclaration.parent.parent;
newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration);
if ((<VariableDeclarationList>ctorDeclaration.parent).declarations.length === 1) {
copyComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
copyLeadingComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
changes.delete(sourceFile, precedingNode);
}
else {
@@ -48,7 +48,7 @@ namespace ts.codefix {
return undefined;
}
copyComments(ctorDeclaration, newClassDeclaration, sourceFile);
copyLeadingComments(ctorDeclaration, newClassDeclaration, sourceFile);
// Because the preceding node could be touched, we need to insert nodes before delete nodes.
changes.insertNodeAfter(sourceFile, precedingNode!, newClassDeclaration);
@@ -112,7 +112,7 @@ namespace ts.codefix {
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
copyComments(assignmentBinaryExpression, method, sourceFile);
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
return method;
}
@@ -132,7 +132,7 @@ namespace ts.codefix {
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(arrowFunction, SyntaxKind.AsyncKeyword));
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
copyComments(assignmentBinaryExpression, method, sourceFile);
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
return method;
}
@@ -143,7 +143,7 @@ namespace ts.codefix {
}
const prop = createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined,
/*type*/ undefined, assignmentBinaryExpression.right);
copyComments(assignmentBinaryExpression.parent, prop, sourceFile);
copyLeadingComments(assignmentBinaryExpression.parent, prop, sourceFile);
return prop;
}
}

View File

@@ -304,30 +304,6 @@ namespace ts.codefix {
}
}
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
const checker = program.getTypeChecker();
let typeIsAccessible = true;
const notAccessible = () => { typeIsAccessible = false; };
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
trackSymbol: (symbol, declaration, meaning) => {
// TODO: GH#18217
typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible;
},
reportInaccessibleThisError: notAccessible,
reportPrivateInBaseOfClassExpression: notAccessible,
reportInaccessibleUniqueSymbolError: notAccessible,
moduleResolverHost: {
readFile: host.readFile,
fileExists: host.fileExists,
directoryExists: host.directoryExists,
getSourceFiles: program.getSourceFiles,
getCurrentDirectory: program.getCurrentDirectory,
getCommonSourceDirectory: program.getCommonSourceDirectory,
}
});
return typeIsAccessible ? res : undefined;
}
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): ReadonlyArray<Identifier> {
// Position shouldn't matter since token is not a SourceFile.
return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry =>

View File

@@ -68,8 +68,8 @@ namespace ts.OrganizeImports {
else {
// Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, {
useNonAdjustedStartPosition: true, // Leave header comment in place
useNonAdjustedEndPosition: false,
leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place
trailingTriviaOption: textChanges.TrailingTriviaOption.Include,
suffix: getNewLineOrDefaultFromHost(host, formatContext.options),
});
}

View File

@@ -48,13 +48,13 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
const returnStatement = createReturn(expression);
body = createBlock([returnStatement], /* multiLine */ true);
suppressLeadingAndTrailingTrivia(body);
copyComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
}
else if (actionName === removeBracesActionName && returnStatement) {
const actualExpression = expression || createVoidZero();
body = needsParentheses(actualExpression) ? createParen(actualExpression) : actualExpression;
suppressLeadingAndTrailingTrivia(body);
copyComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
}
else {
Debug.fail("invalid action");

View File

@@ -0,0 +1,520 @@
/* @internal */
namespace ts.refactor.convertToNamedParameters {
const refactorName = "Convert to named parameters";
const minimumParameterLength = 2;
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
function getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo> {
const { file, startPosition } = context;
const isJSFile = isSourceFileJS(file);
if (isJSFile) return emptyArray; // TODO: GH#30113
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker());
if (!functionDeclaration) return emptyArray;
const description = getLocaleSpecificMessage(Diagnostics.Convert_to_named_parameters);
return [{
name: refactorName,
description,
actions: [{
name: refactorName,
description
}]
}];
}
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
Debug.assert(actionName === refactorName);
const { file, startPosition, program, cancellationToken, host } = context;
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker());
if (!functionDeclaration || !cancellationToken) return undefined;
const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken);
if (groupedReferences.valid) {
const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences));
return { renameFilename: undefined, renameLocation: undefined, edits };
}
return { edits: [] }; // TODO: GH#30113
}
function doChange(
sourceFile: SourceFile,
program: Program,
host: LanguageServiceHost,
changes: textChanges.ChangeTracker,
functionDeclaration: ValidFunctionDeclaration,
groupedReferences: GroupedReferences): void {
const newParamDeclaration = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param));
changes.replaceNodeRangeWithNodes(
sourceFile,
first(functionDeclaration.parameters),
last(functionDeclaration.parameters),
newParamDeclaration,
{ joiner: ", ",
// indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter
indentation: 0,
leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll,
trailingTriviaOption: textChanges.TrailingTriviaOption.Include
});
const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos));
for (const call of functionCalls) {
if (call.arguments && call.arguments.length) {
const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true);
changes.replaceNodeRange(
getSourceFileOfNode(call),
first(call.arguments),
last(call.arguments),
newArgument,
{ leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include });
}
}
}
function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences {
const functionNames = getFunctionNames(functionDeclaration);
const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : [];
const names = deduplicate([...functionNames, ...classNames], equateValues);
const checker = program.getTypeChecker();
const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken));
const groupedReferences = groupReferences(references);
if (!every(groupedReferences.declarations, decl => contains(names, decl))) {
groupedReferences.valid = false;
}
return groupedReferences;
function groupReferences(referenceEntries: ReadonlyArray<FindAllReferences.Entry>): GroupedReferences {
const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] };
const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true };
const functionSymbols = map(functionNames, checker.getSymbolAtLocation);
const classSymbols = map(classNames, checker.getSymbolAtLocation);
const isConstructor = isConstructorDeclaration(functionDeclaration);
for (const entry of referenceEntries) {
if (entry.kind !== FindAllReferences.EntryKind.Node) {
groupedReferences.valid = false;
continue;
}
if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
const decl = entryToDeclaration(entry);
if (decl) {
groupedReferences.declarations.push(decl);
continue;
}
const call = entryToFunctionCall(entry);
if (call) {
groupedReferences.functionCalls.push(call);
continue;
}
}
// if the refactored function is a constructor, we must also check if the references to its class are valid
if (isConstructor && contains(classSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
const decl = entryToDeclaration(entry);
if (decl) {
groupedReferences.declarations.push(decl);
continue;
}
const accessExpression = entryToAccessExpression(entry);
if (accessExpression) {
classReferences.accessExpressions.push(accessExpression);
continue;
}
// Only class declarations are allowed to be used as a type (in a heritage clause),
// otherwise `findAllReferences` might not be able to track constructor calls.
if (isClassDeclaration(functionDeclaration.parent)) {
const type = entryToType(entry);
if (type) {
classReferences.typeUsages.push(type);
continue;
}
}
}
groupedReferences.valid = false;
}
return groupedReferences;
}
}
function symbolComparer(a: Symbol, b: Symbol): boolean {
return getSymbolTarget(a) === getSymbolTarget(b);
}
function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined {
if (isDeclaration(entry.node.parent)) {
return entry.node;
}
return undefined;
}
function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined {
if (entry.node.parent) {
const functionReference = entry.node;
const parent = functionReference.parent;
switch (parent.kind) {
// Function call (foo(...) or super(...))
case SyntaxKind.CallExpression:
const callExpression = tryCast(parent, isCallExpression);
if (callExpression && callExpression.expression === functionReference) {
return callExpression;
}
break;
// Constructor call (new Foo(...))
case SyntaxKind.NewExpression:
const newExpression = tryCast(parent, isNewExpression);
if (newExpression && newExpression.expression === functionReference) {
return newExpression;
}
break;
// Method call (x.foo(...))
case SyntaxKind.PropertyAccessExpression:
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) {
const callExpression = tryCast(propertyAccessExpression.parent, isCallExpression);
if (callExpression && callExpression.expression === propertyAccessExpression) {
return callExpression;
}
}
break;
// Method call (x["foo"](...))
case SyntaxKind.ElementAccessExpression:
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) {
const callExpression = tryCast(elementAccessExpression.parent, isCallExpression);
if (callExpression && callExpression.expression === elementAccessExpression) {
return callExpression;
}
}
break;
}
}
return undefined;
}
function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined {
if (entry.node.parent) {
const reference = entry.node;
const parent = reference.parent;
switch (parent.kind) {
// `C.foo`
case SyntaxKind.PropertyAccessExpression:
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
if (propertyAccessExpression && propertyAccessExpression.expression === reference) {
return propertyAccessExpression;
}
break;
// `C["foo"]`
case SyntaxKind.ElementAccessExpression:
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
if (elementAccessExpression && elementAccessExpression.expression === reference) {
return elementAccessExpression;
}
break;
}
}
return undefined;
}
function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined {
const reference = entry.node;
if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) {
return reference;
}
return undefined;
}
function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined {
const node = getTouchingToken(file, startPosition);
const functionDeclaration = getContainingFunction(node);
if (functionDeclaration
&& isValidFunctionDeclaration(functionDeclaration, checker)
&& rangeContainsRange(functionDeclaration, node)
&& !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration;
return undefined;
}
function isValidFunctionDeclaration(functionDeclaration: SignatureDeclaration, checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration {
if (!isValidParameterNodeArray(functionDeclaration.parameters)) return false;
switch (functionDeclaration.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
return !!functionDeclaration.name && !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration);
case SyntaxKind.Constructor:
if (isClassDeclaration(functionDeclaration.parent)) {
return !!functionDeclaration.body && !!functionDeclaration.parent.name && !checker.isImplementationOfOverload(functionDeclaration);
}
else {
return isValidVariableDeclaration(functionDeclaration.parent.parent) && !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration);
}
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
return isValidVariableDeclaration(functionDeclaration.parent);
}
return false;
}
function isValidParameterNodeArray(parameters: NodeArray<ParameterDeclaration>): parameters is ValidParameterNodeArray {
return getRefactorableParametersLength(parameters) >= minimumParameterLength && every(parameters, isValidParameterDeclaration);
}
function isValidParameterDeclaration(paramDeclaration: ParameterDeclaration): paramDeclaration is ValidParameterDeclaration {
return !paramDeclaration.modifiers && !paramDeclaration.decorators && isIdentifier(paramDeclaration.name);
}
function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration {
return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113
}
function hasThisParameter(parameters: NodeArray<ParameterDeclaration>): boolean {
return parameters.length > 0 && isThis(parameters[0].name);
}
function getRefactorableParametersLength(parameters: NodeArray<ParameterDeclaration>): number {
if (hasThisParameter(parameters)) {
return parameters.length - 1;
}
return parameters.length;
}
function getRefactorableParameters(parameters: NodeArray<ValidParameterDeclaration>): NodeArray<ValidParameterDeclaration> {
if (hasThisParameter(parameters)) {
parameters = createNodeArray(parameters.slice(1), parameters.hasTrailingComma);
}
return parameters;
}
function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray<Expression>): ObjectLiteralExpression {
const parameters = getRefactorableParameters(functionDeclaration.parameters);
const hasRestParameter = isRestParameter(last(parameters));
const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments;
const properties = map(nonRestArguments, (arg, i) => {
const property = createPropertyAssignment(getParameterName(parameters[i]), arg);
suppressLeadingAndTrailingTrivia(property.initializer);
copyComments(arg, property);
return property;
});
if (hasRestParameter && functionArguments.length >= parameters.length) {
const restArguments = functionArguments.slice(parameters.length - 1);
const restProperty = createPropertyAssignment(getParameterName(last(parameters)), createArrayLiteral(restArguments));
properties.push(restProperty);
}
const objectLiteral = createObjectLiteral(properties, /*multiLine*/ false);
return objectLiteral;
}
function createNewParameters(functionDeclaration: ValidFunctionDeclaration, program: Program, host: LanguageServiceHost): NodeArray<ParameterDeclaration> {
const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters);
const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration);
const objectParameterName = createObjectBindingPattern(bindingElements);
const objectParameterType = createParameterTypeNode(refactorableParameters);
const checker = program.getTypeChecker();
let objectInitializer: Expression | undefined;
// If every parameter in the original function was optional, add an empty object initializer to the new object parameter
if (every(refactorableParameters, checker.isOptionalParameter)) {
objectInitializer = createObjectLiteral();
}
const objectParameter = createParameter(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined,
objectParameterName,
/*questionToken*/ undefined,
objectParameterType,
objectInitializer);
if (hasThisParameter(functionDeclaration.parameters)) {
const thisParameter = functionDeclaration.parameters[0];
const newThisParameter = createParameter(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*dotDotDotToken*/ undefined,
thisParameter.name,
/*questionToken*/ undefined,
thisParameter.type);
suppressLeadingAndTrailingTrivia(newThisParameter.name);
copyComments(thisParameter.name, newThisParameter.name);
if (thisParameter.type) {
suppressLeadingAndTrailingTrivia(newThisParameter.type!);
copyComments(thisParameter.type, newThisParameter.type!);
}
return createNodeArray([newThisParameter, objectParameter]);
}
return createNodeArray([objectParameter]);
function createParameterTypeNode(parameters: NodeArray<ValidParameterDeclaration>): TypeLiteralNode {
const members = map(parameters, createPropertySignatureFromParameterDeclaration);
const typeNode = addEmitFlags(createTypeLiteralNode(members), EmitFlags.SingleLine);
return typeNode;
}
function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature {
let parameterType = parameterDeclaration.type;
if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) {
parameterType = getTypeNode(parameterDeclaration);
}
const propertySignature = createPropertySignature(
/*modifiers*/ undefined,
getParameterName(parameterDeclaration),
parameterDeclaration.initializer || isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken,
parameterType,
/*initializer*/ undefined);
suppressLeadingAndTrailingTrivia(propertySignature);
copyComments(parameterDeclaration.name, propertySignature.name);
if (parameterDeclaration.type && propertySignature.type) {
copyComments(parameterDeclaration.type, propertySignature.type);
}
return propertySignature;
}
function getTypeNode(node: Node): TypeNode | undefined {
const checker = program.getTypeChecker();
const type = checker.getTypeAtLocation(node);
return getTypeNodeIfAccessible(type, node, program, host);
}
}
function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement {
const element = createBindingElement(
/*dotDotDotToken*/ undefined,
/*propertyName*/ undefined,
getParameterName(parameterDeclaration),
isRestParameter(parameterDeclaration) ? createArrayLiteral() : parameterDeclaration.initializer);
suppressLeadingAndTrailingTrivia(element);
if (parameterDeclaration.initializer && element.initializer) {
copyComments(parameterDeclaration.initializer, element.initializer);
}
return element;
}
function copyComments(sourceNode: Node, targetNode: Node) {
const sourceFile = sourceNode.getSourceFile();
const text = sourceFile.text;
if (hasLeadingLineBreak(sourceNode, text)) {
copyLeadingComments(sourceNode, targetNode, sourceFile);
}
else {
copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile);
}
copyTrailingComments(sourceNode, targetNode, sourceFile);
}
function hasLeadingLineBreak(node: Node, text: string) {
const start = node.getFullStart();
const end = node.getStart();
for (let i = start; i < end; i++) {
if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true;
}
return false;
}
function getParameterName(paramDeclaration: ValidParameterDeclaration) {
return getTextOfIdentifierOrLiteral(paramDeclaration.name);
}
function getClassNames(constructorDeclaration: ValidConstructor): Identifier[] {
switch (constructorDeclaration.parent.kind) {
case SyntaxKind.ClassDeclaration:
const classDeclaration = constructorDeclaration.parent;
return [classDeclaration.name];
case SyntaxKind.ClassExpression:
const classExpression = constructorDeclaration.parent;
const variableDeclaration = constructorDeclaration.parent.parent;
const className = classExpression.name;
if (className) return [className, variableDeclaration.name];
return [variableDeclaration.name];
}
}
function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] {
switch (functionDeclaration.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
return [functionDeclaration.name];
case SyntaxKind.Constructor:
const ctrKeyword = findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile())!;
if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) {
const variableDeclaration = functionDeclaration.parent.parent;
return [variableDeclaration.name, ctrKeyword];
}
return [ctrKeyword];
case SyntaxKind.ArrowFunction:
return [functionDeclaration.parent.name];
case SyntaxKind.FunctionExpression:
if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name];
return [functionDeclaration.parent.name];
default:
return Debug.assertNever(functionDeclaration);
}
}
type ValidParameterNodeArray = NodeArray<ValidParameterDeclaration>;
interface ValidVariableDeclaration extends VariableDeclaration {
name: Identifier;
type: undefined;
}
interface ValidConstructor extends ConstructorDeclaration {
parent: (ClassDeclaration & { name: Identifier }) | (ClassExpression & { parent: ValidVariableDeclaration });
parameters: NodeArray<ValidParameterDeclaration>;
body: FunctionBody;
}
interface ValidFunction extends FunctionDeclaration {
name: Identifier;
parameters: NodeArray<ValidParameterDeclaration>;
body: FunctionBody;
}
interface ValidMethod extends MethodDeclaration {
parameters: NodeArray<ValidParameterDeclaration>;
body: FunctionBody;
}
interface ValidFunctionExpression extends FunctionExpression {
parent: ValidVariableDeclaration;
parameters: NodeArray<ValidParameterDeclaration>;
}
interface ValidArrowFunction extends ArrowFunction {
parent: ValidVariableDeclaration;
parameters: NodeArray<ValidParameterDeclaration>;
}
type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression;
interface ValidParameterDeclaration extends ParameterDeclaration {
name: Identifier;
modifiers: undefined;
decorators: undefined;
}
interface GroupedReferences {
functionCalls: (CallExpression | NewExpression)[];
declarations: Node[];
classReferences?: ClassReferences;
valid: boolean;
}
interface ClassReferences {
accessExpressions: Node[];
typeUsages: Node[];
}
}

View File

@@ -1798,7 +1798,7 @@ namespace ts {
const span = createTextSpanFromBounds(start, end);
const formatContext = formatting.getFormatContext(formatOptions);
return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => {
return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => {
cancellationToken.throwIfCancellationRequested();
return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences });
});

View File

@@ -28,17 +28,27 @@ namespace ts.textChanges {
}
export interface ConfigurableStart {
/** True to use getStart() (NB, not getFullStart()) without adjustment. */
useNonAdjustedStartPosition?: boolean;
leadingTriviaOption?: LeadingTriviaOption;
}
export interface ConfigurableEnd {
/** True to use getEnd() without adjustment. */
useNonAdjustedEndPosition?: boolean;
trailingTriviaOption?: TrailingTriviaOption;
}
export enum Position {
FullStart,
Start
export enum LeadingTriviaOption {
/** Exclude all leading trivia (use getStart()) */
Exclude,
/** Include leading trivia and,
* if there are no line breaks between the node and the previous token,
* include all trivia between the node and the previous token
*/
IncludeAll,
}
export enum TrailingTriviaOption {
/** Exclude all trailing trivia (use getEnd()) */
Exclude,
/** Include trailing trivia */
Include,
}
function skipWhitespacesAndLineBreaks(text: string, start: number) {
@@ -68,13 +78,14 @@ namespace ts.textChanges {
* Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding
* variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline).
* By default when removing nodes we adjust start and end positions to respect specification of the trivia above.
* If pos\end should be interpreted literally 'useNonAdjustedStartPosition' or 'useNonAdjustedEndPosition' should be set to true
* If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude`
* and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`.
*/
export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {}
export const useNonAdjustedPositions: ConfigurableStartEnd = {
useNonAdjustedStartPosition: true,
useNonAdjustedEndPosition: true,
const useNonAdjustedPositions: ConfigurableStartEnd = {
leadingTriviaOption: LeadingTriviaOption.Exclude,
trailingTriviaOption: TrailingTriviaOption.Exclude,
};
export interface InsertNodeOptions {
@@ -143,11 +154,12 @@ namespace ts.textChanges {
}
function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange {
return { pos: getAdjustedStartPosition(sourceFile, startNode, options, Position.Start), end: getAdjustedEndPosition(sourceFile, endNode, options) };
return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) };
}
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) {
if (options.useNonAdjustedStartPosition) {
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart) {
const { leadingTriviaOption } = options;
if (leadingTriviaOption === LeadingTriviaOption.Exclude) {
return node.getStart(sourceFile);
}
const fullStart = node.getFullStart();
@@ -165,7 +177,7 @@ namespace ts.textChanges {
// fullstart
// when b is replaced - we usually want to keep the leading trvia
// when b is deleted - we delete it
return position === Position.Start ? start : fullStart;
return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start;
}
// get start position of the line following the line that contains fullstart position
// (but only if the fullstart isn't the very beginning of the file)
@@ -178,11 +190,12 @@ namespace ts.textChanges {
function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd) {
const { end } = node;
if (options.useNonAdjustedEndPosition || isExpression(node)) {
const { trailingTriviaOption } = options;
if (trailingTriviaOption === TrailingTriviaOption.Exclude || (isExpression(node) && trailingTriviaOption !== TrailingTriviaOption.Include)) {
return end;
}
const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true);
return newEnd !== end && isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))
return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1)))
? newEnd
: end;
}
@@ -240,15 +253,15 @@ namespace ts.textChanges {
this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) });
}
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}): void {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = {}): void {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options, Position.FullStart);
public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options);
const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options);
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
@@ -307,7 +320,7 @@ namespace ts.textChanges {
}
public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false): void {
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}, Position.Start), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
}
public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void {
@@ -427,7 +440,7 @@ namespace ts.textChanges {
}
public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void {
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}, Position.Start);
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {});
this.insertNodeAt(sourceFile, pos, newNode, {
prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter,
suffix: this.newLineCharacter
@@ -736,7 +749,7 @@ namespace ts.textChanges {
// find first non-whitespace position in the leading trivia of the node
function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number {
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
}
function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number, number] {
@@ -1090,7 +1103,7 @@ namespace ts.textChanges {
case SyntaxKind.ImportDeclaration:
deleteNode(changes, sourceFile, node,
// For first import, leave header comment in place
node === sourceFile.imports[0].parent ? { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: false } : undefined);
node === sourceFile.imports[0].parent ? { leadingTriviaOption: LeadingTriviaOption.Exclude } : undefined);
break;
case SyntaxKind.BindingElement:
@@ -1134,7 +1147,7 @@ namespace ts.textChanges {
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
}
else {
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { useNonAdjustedEndPosition: true } : undefined);
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { trailingTriviaOption: TrailingTriviaOption.Exclude } : undefined);
}
}
}
@@ -1213,8 +1226,8 @@ namespace ts.textChanges {
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
// Exported for tests only! (TODO: improve tests to not need this)
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}): void {
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
const startPosition = getAdjustedStartPosition(sourceFile, node, options);
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}

View File

@@ -84,6 +84,7 @@
"refactors/generateGetAccessorAndSetAccessor.ts",
"refactors/moveToNewFile.ts",
"refactors/addOrRemoveBracesToArrowFunction.ts",
"refactors/convertToNamedParameters.ts",
"services.ts",
"breakpoints.ts",
"transform.ts",

View File

@@ -1664,6 +1664,18 @@ namespace ts {
return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName));
}
export function getSymbolTarget(symbol: Symbol): Symbol {
let next: Symbol = symbol;
while (isTransientSymbol(next) && next.target) {
next = next.target;
}
return next;
}
function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol {
return (symbol.flags & SymbolFlags.Transient) !== 0;
}
export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) {
return getSymbolId(skipAlias(symbol, checker));
}
@@ -1821,8 +1833,28 @@ namespace ts {
return lastPos;
}
export function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
}
export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment));
}
/**
* This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`.
* This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the
* notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.:
* `function foo(\* not leading comment for a *\ a: string) {}`
* The comment refers to `a` but belongs to the `(` token, but we might want to copy it.
*/
export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
}
function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) {
return (pos: number, end: number, kind: CommentKind, htnl: boolean) => {
if (kind === SyntaxKind.MultiLineCommentTrivia) {
// Remove leading /*
pos += 2;
@@ -1833,8 +1865,8 @@ namespace ts {
// Remove leading //
pos += 2;
}
addSyntheticLeadingComment(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
});
cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
};
}
function indexInTextChange(change: string, name: string): number {
@@ -1914,4 +1946,28 @@ namespace ts {
export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined {
return checker.getTypeAtLocation(caseClause.parent.parent.expression);
}
export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
const checker = program.getTypeChecker();
let typeIsAccessible = true;
const notAccessible = () => { typeIsAccessible = false; };
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
trackSymbol: (symbol, declaration, meaning) => {
// TODO: GH#18217
typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible;
},
reportInaccessibleThisError: notAccessible,
reportPrivateInBaseOfClassExpression: notAccessible,
reportInaccessibleUniqueSymbolError: notAccessible,
moduleResolverHost: {
readFile: host.readFile,
fileExists: host.fileExists,
directoryExists: host.directoryExists,
getSourceFiles: program.getSourceFiles,
getCurrentDirectory: program.getCurrentDirectory,
getCommonSourceDirectory: program.getCommonSourceDirectory,
}
});
return typeIsAccessible ? res : undefined;
}
}