initial revision of infrastructure to produce text changes that uses existing node factory, formatter and printer (#14441)

initial revision of infrastructure to produce text changes that uses existing node factory, formatter and printer
This commit is contained in:
Vladimir Matveev 2017-03-15 11:44:36 -07:00 committed by GitHub
parent ea57fbc59f
commit 2b10611fbf
118 changed files with 3015 additions and 346 deletions

View File

@ -11,11 +11,7 @@ var ts = require("./lib/typescript");
// Variables
var compilerDirectory = "src/compiler/";
var servicesDirectory = "src/services/";
var serverDirectory = "src/server/";
var typingsInstallerDirectory = "src/server/typingsInstaller";
var cancellationTokenDirectory = "src/server/cancellationToken";
var watchGuardDirectory = "src/server/watchGuard";
var harnessDirectory = "src/harness/";
var libraryDirectory = "src/lib/";
var scriptsDirectory = "scripts/";
@ -131,6 +127,7 @@ var harnessSources = harnessCoreSources.concat([
"matchFiles.ts",
"initializeTSConfig.ts",
"printer.ts",
"textChanges.ts",
"transform.ts",
"customTransforms.ts",
].map(function (f) {

View File

@ -199,6 +199,8 @@ namespace ts {
onEmitHelpers,
onSetSourceFile,
substituteNode,
onBeforeEmitNodeArray,
onAfterEmitNodeArray
} = handlers;
const newLine = getNewLineCharacter(printerOptions);
@ -631,6 +633,11 @@ namespace ts {
if (isExpression(node)) {
return pipelineEmitExpression(trySubstituteNode(EmitHint.Expression, node));
}
if (isToken(node)) {
writeTokenText(kind);
return;
}
}
function pipelineEmitExpression(node: Node): void {
@ -1553,6 +1560,10 @@ namespace ts {
emitSignatureAndBody(node, emitSignatureHead);
}
function emitBlockCallback(_hint: EmitHint, body: Node): void {
emitBlockFunctionBody(<Block>body);
}
function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) {
const body = node.body;
if (body) {
@ -1564,12 +1575,22 @@ namespace ts {
if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) {
emitSignatureHead(node);
emitBlockFunctionBody(body);
if (onEmitNode) {
onEmitNode(EmitHint.Unspecified, body, emitBlockCallback);
}
else {
emitBlockFunctionBody(body);
}
}
else {
pushNameGenerationScope();
emitSignatureHead(node);
emitBlockFunctionBody(body);
if (onEmitNode) {
onEmitNode(EmitHint.Unspecified, body, emitBlockCallback);
}
else {
emitBlockFunctionBody(body);
}
popNameGenerationScope();
}
@ -2200,6 +2221,10 @@ namespace ts {
write(getOpeningBracket(format));
}
if (onBeforeEmitNodeArray) {
onBeforeEmitNodeArray(children);
}
if (isEmpty) {
// Write a line terminator if the parent node was multi-line
if (format & ListFormat.MultiLine) {
@ -2315,6 +2340,10 @@ namespace ts {
}
}
if (onAfterEmitNodeArray) {
onAfterEmitNodeArray(children);
}
if (format & ListFormat.BracketsMask) {
write(getClosingBracket(format));
}

View File

@ -1099,6 +1099,10 @@ namespace ts {
: node;
}
export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode {
return <KeywordTypeNode>createSynthesizedNode(kind);
}
export function createFunctionDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) {
const node = <FunctionDeclaration>createSynthesizedNode(SyntaxKind.FunctionDeclaration);
node.decorators = asNodeArray(decorators);

View File

@ -333,7 +333,7 @@ namespace ts {
}
/* @internal */
export function getLineStarts(sourceFile: SourceFile): number[] {
export function getLineStarts(sourceFile: SourceFileLike): number[] {
return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text));
}

View File

@ -2205,6 +2205,16 @@ namespace ts {
name: string;
}
/* @internal */
/**
* Subset of properties from SourceFile that are used in multiple utility functions
*/
export interface SourceFileLike {
readonly text: string;
lineMap: number[];
}
// Source files are declarations when they are external modules.
export interface SourceFile extends Declaration {
kind: SyntaxKind.SourceFile;
@ -4132,6 +4142,8 @@ namespace ts {
/*@internal*/ onEmitSourceMapOfPosition?: (pos: number) => void;
/*@internal*/ onEmitHelpers?: (node: Node, writeLines: (text: string) => void) => void;
/*@internal*/ onSetSourceFile?: (node: SourceFile) => void;
/*@internal*/ onBeforeEmitNodeArray?: (nodes: NodeArray<any>) => void;
/*@internal*/ onAfterEmitNodeArray?: (nodes: NodeArray<any>) => void;
}
export interface PrinterOptions {

View File

@ -184,7 +184,7 @@ namespace ts {
return false;
}
export function getStartPositionOfLine(line: number, sourceFile: SourceFile): number {
export function getStartPositionOfLine(line: number, sourceFile: SourceFileLike): number {
Debug.assert(line >= 0);
return getLineStarts(sourceFile)[line];
}
@ -204,7 +204,7 @@ namespace ts {
return value !== undefined;
}
export function getEndLinePosition(line: number, sourceFile: SourceFile): number {
export function getEndLinePosition(line: number, sourceFile: SourceFileLike): number {
Debug.assert(line >= 0);
const lineStarts = getLineStarts(sourceFile);
@ -255,7 +255,11 @@ namespace ts {
return !nodeIsMissing(node);
}
export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile, includeJsDoc?: boolean): number {
export function isToken(n: Node): boolean {
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
}
export function getTokenPosOfNode(node: Node, sourceFile?: SourceFileLike, includeJsDoc?: boolean): number {
// With nodes that have no width (i.e. 'Missing' nodes), we actually *don't*
// want to skip trivia because this will launch us forward to the next token.
if (nodeIsMissing(node)) {
@ -289,7 +293,7 @@ namespace ts {
return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode;
}
export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFile): number {
export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFileLike): number {
if (nodeIsMissing(node) || !node.decorators) {
return getTokenPosOfNode(node, sourceFile);
}
@ -2491,7 +2495,7 @@ namespace ts {
return indentStrings[1].length;
}
export function createTextWriter(newLine: String): EmitTextWriter {
export function createTextWriter(newLine: string): EmitTextWriter {
let output: string;
let indent: number;
let lineStart: boolean;

View File

@ -154,9 +154,9 @@ namespace ts {
* Starts a new lexical environment and visits a parameter list, suspending the lexical
* environment upon completion.
*/
export function visitParameterList(nodes: NodeArray<ParameterDeclaration>, visitor: Visitor, context: TransformationContext) {
export function visitParameterList(nodes: NodeArray<ParameterDeclaration>, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes) {
context.startLexicalEnvironment();
const updated = visitNodes(nodes, visitor, isParameterDeclaration);
const updated = nodesVisitor(nodes, visitor, isParameterDeclaration);
context.suspendLexicalEnvironment();
return updated;
}
@ -204,9 +204,9 @@ namespace ts {
* @param visitor The callback used to visit each child.
* @param context A lexical environment context for the visitor.
*/
export function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor, context: TransformationContext): T | undefined;
export function visitEachChild<T extends Node>(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes): T | undefined;
export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext): Node {
export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes): Node {
if (node === undefined) {
return undefined;
}
@ -243,8 +243,8 @@ namespace ts {
// Signature elements
case SyntaxKind.Parameter:
return updateParameter(<ParameterDeclaration>node,
visitNodes((<ParameterDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ParameterDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<ParameterDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ParameterDeclaration>node).modifiers, visitor, isModifier),
(<ParameterDeclaration>node).dotDotDotToken,
visitNode((<ParameterDeclaration>node).name, visitor, isBindingName),
visitNode((<ParameterDeclaration>node).type, visitor, isTypeNode),
@ -257,55 +257,55 @@ namespace ts {
// Type member
case SyntaxKind.PropertyDeclaration:
return updateProperty(<PropertyDeclaration>node,
visitNodes((<PropertyDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<PropertyDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<PropertyDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<PropertyDeclaration>node).modifiers, visitor, isModifier),
visitNode((<PropertyDeclaration>node).name, visitor, isPropertyName),
visitNode((<PropertyDeclaration>node).type, visitor, isTypeNode),
visitNode((<PropertyDeclaration>node).initializer, visitor, isExpression));
case SyntaxKind.MethodDeclaration:
return updateMethod(<MethodDeclaration>node,
visitNodes((<MethodDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<MethodDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<MethodDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<MethodDeclaration>node).modifiers, visitor, isModifier),
(<MethodDeclaration>node).asteriskToken,
visitNode((<MethodDeclaration>node).name, visitor, isPropertyName),
visitNodes((<MethodDeclaration>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<MethodDeclaration>node).parameters, visitor, context),
nodesVisitor((<MethodDeclaration>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<MethodDeclaration>node).parameters, visitor, context, nodesVisitor),
visitNode((<MethodDeclaration>node).type, visitor, isTypeNode),
visitFunctionBody((<MethodDeclaration>node).body, visitor, context));
case SyntaxKind.Constructor:
return updateConstructor(<ConstructorDeclaration>node,
visitNodes((<ConstructorDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ConstructorDeclaration>node).modifiers, visitor, isModifier),
visitParameterList((<ConstructorDeclaration>node).parameters, visitor, context),
nodesVisitor((<ConstructorDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ConstructorDeclaration>node).modifiers, visitor, isModifier),
visitParameterList((<ConstructorDeclaration>node).parameters, visitor, context, nodesVisitor),
visitFunctionBody((<ConstructorDeclaration>node).body, visitor, context));
case SyntaxKind.GetAccessor:
return updateGetAccessor(<GetAccessorDeclaration>node,
visitNodes((<GetAccessorDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<GetAccessorDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<GetAccessorDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<GetAccessorDeclaration>node).modifiers, visitor, isModifier),
visitNode((<GetAccessorDeclaration>node).name, visitor, isPropertyName),
visitParameterList((<GetAccessorDeclaration>node).parameters, visitor, context),
visitParameterList((<GetAccessorDeclaration>node).parameters, visitor, context, nodesVisitor),
visitNode((<GetAccessorDeclaration>node).type, visitor, isTypeNode),
visitFunctionBody((<GetAccessorDeclaration>node).body, visitor, context));
case SyntaxKind.SetAccessor:
return updateSetAccessor(<SetAccessorDeclaration>node,
visitNodes((<SetAccessorDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<SetAccessorDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<SetAccessorDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<SetAccessorDeclaration>node).modifiers, visitor, isModifier),
visitNode((<SetAccessorDeclaration>node).name, visitor, isPropertyName),
visitParameterList((<SetAccessorDeclaration>node).parameters, visitor, context),
visitParameterList((<SetAccessorDeclaration>node).parameters, visitor, context, nodesVisitor),
visitFunctionBody((<SetAccessorDeclaration>node).body, visitor, context));
// Binding patterns
case SyntaxKind.ObjectBindingPattern:
return updateObjectBindingPattern(<ObjectBindingPattern>node,
visitNodes((<ObjectBindingPattern>node).elements, visitor, isBindingElement));
nodesVisitor((<ObjectBindingPattern>node).elements, visitor, isBindingElement));
case SyntaxKind.ArrayBindingPattern:
return updateArrayBindingPattern(<ArrayBindingPattern>node,
visitNodes((<ArrayBindingPattern>node).elements, visitor, isArrayBindingElement));
nodesVisitor((<ArrayBindingPattern>node).elements, visitor, isArrayBindingElement));
case SyntaxKind.BindingElement:
return updateBindingElement(<BindingElement>node,
@ -317,11 +317,11 @@ namespace ts {
// Expression
case SyntaxKind.ArrayLiteralExpression:
return updateArrayLiteral(<ArrayLiteralExpression>node,
visitNodes((<ArrayLiteralExpression>node).elements, visitor, isExpression));
nodesVisitor((<ArrayLiteralExpression>node).elements, visitor, isExpression));
case SyntaxKind.ObjectLiteralExpression:
return updateObjectLiteral(<ObjectLiteralExpression>node,
visitNodes((<ObjectLiteralExpression>node).properties, visitor, isObjectLiteralElementLike));
nodesVisitor((<ObjectLiteralExpression>node).properties, visitor, isObjectLiteralElementLike));
case SyntaxKind.PropertyAccessExpression:
return updatePropertyAccess(<PropertyAccessExpression>node,
@ -336,14 +336,14 @@ namespace ts {
case SyntaxKind.CallExpression:
return updateCall(<CallExpression>node,
visitNode((<CallExpression>node).expression, visitor, isExpression),
visitNodes((<CallExpression>node).typeArguments, visitor, isTypeNode),
visitNodes((<CallExpression>node).arguments, visitor, isExpression));
nodesVisitor((<CallExpression>node).typeArguments, visitor, isTypeNode),
nodesVisitor((<CallExpression>node).arguments, visitor, isExpression));
case SyntaxKind.NewExpression:
return updateNew(<NewExpression>node,
visitNode((<NewExpression>node).expression, visitor, isExpression),
visitNodes((<NewExpression>node).typeArguments, visitor, isTypeNode),
visitNodes((<NewExpression>node).arguments, visitor, isExpression));
nodesVisitor((<NewExpression>node).typeArguments, visitor, isTypeNode),
nodesVisitor((<NewExpression>node).arguments, visitor, isExpression));
case SyntaxKind.TaggedTemplateExpression:
return updateTaggedTemplate(<TaggedTemplateExpression>node,
@ -361,19 +361,19 @@ namespace ts {
case SyntaxKind.FunctionExpression:
return updateFunctionExpression(<FunctionExpression>node,
visitNodes((<FunctionExpression>node).modifiers, visitor, isModifier),
nodesVisitor((<FunctionExpression>node).modifiers, visitor, isModifier),
(<FunctionExpression>node).asteriskToken,
visitNode((<FunctionExpression>node).name, visitor, isIdentifier),
visitNodes((<FunctionExpression>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<FunctionExpression>node).parameters, visitor, context),
nodesVisitor((<FunctionExpression>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<FunctionExpression>node).parameters, visitor, context, nodesVisitor),
visitNode((<FunctionExpression>node).type, visitor, isTypeNode),
visitFunctionBody((<FunctionExpression>node).body, visitor, context));
case SyntaxKind.ArrowFunction:
return updateArrowFunction(<ArrowFunction>node,
visitNodes((<ArrowFunction>node).modifiers, visitor, isModifier),
visitNodes((<ArrowFunction>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<ArrowFunction>node).parameters, visitor, context),
nodesVisitor((<ArrowFunction>node).modifiers, visitor, isModifier),
nodesVisitor((<ArrowFunction>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<ArrowFunction>node).parameters, visitor, context, nodesVisitor),
visitNode((<ArrowFunction>node).type, visitor, isTypeNode),
visitFunctionBody((<ArrowFunction>node).body, visitor, context));
@ -415,7 +415,7 @@ namespace ts {
case SyntaxKind.TemplateExpression:
return updateTemplateExpression(<TemplateExpression>node,
visitNode((<TemplateExpression>node).head, visitor, isTemplateHead),
visitNodes((<TemplateExpression>node).templateSpans, visitor, isTemplateSpan));
nodesVisitor((<TemplateExpression>node).templateSpans, visitor, isTemplateSpan));
case SyntaxKind.YieldExpression:
return updateYield(<YieldExpression>node,
@ -428,15 +428,15 @@ namespace ts {
case SyntaxKind.ClassExpression:
return updateClassExpression(<ClassExpression>node,
visitNodes((<ClassExpression>node).modifiers, visitor, isModifier),
nodesVisitor((<ClassExpression>node).modifiers, visitor, isModifier),
visitNode((<ClassExpression>node).name, visitor, isIdentifier),
visitNodes((<ClassExpression>node).typeParameters, visitor, isTypeParameter),
visitNodes((<ClassExpression>node).heritageClauses, visitor, isHeritageClause),
visitNodes((<ClassExpression>node).members, visitor, isClassElement));
nodesVisitor((<ClassExpression>node).typeParameters, visitor, isTypeParameter),
nodesVisitor((<ClassExpression>node).heritageClauses, visitor, isHeritageClause),
nodesVisitor((<ClassExpression>node).members, visitor, isClassElement));
case SyntaxKind.ExpressionWithTypeArguments:
return updateExpressionWithTypeArguments(<ExpressionWithTypeArguments>node,
visitNodes((<ExpressionWithTypeArguments>node).typeArguments, visitor, isTypeNode),
nodesVisitor((<ExpressionWithTypeArguments>node).typeArguments, visitor, isTypeNode),
visitNode((<ExpressionWithTypeArguments>node).expression, visitor, isExpression));
case SyntaxKind.AsExpression:
@ -457,11 +457,11 @@ namespace ts {
// Element
case SyntaxKind.Block:
return updateBlock(<Block>node,
visitNodes((<Block>node).statements, visitor, isStatement));
nodesVisitor((<Block>node).statements, visitor, isStatement));
case SyntaxKind.VariableStatement:
return updateVariableStatement(<VariableStatement>node,
visitNodes((<VariableStatement>node).modifiers, visitor, isModifier),
nodesVisitor((<VariableStatement>node).modifiers, visitor, isModifier),
visitNode((<VariableStatement>node).declarationList, visitor, isVariableDeclarationList));
case SyntaxKind.ExpressionStatement:
@ -549,61 +549,61 @@ namespace ts {
case SyntaxKind.VariableDeclarationList:
return updateVariableDeclarationList(<VariableDeclarationList>node,
visitNodes((<VariableDeclarationList>node).declarations, visitor, isVariableDeclaration));
nodesVisitor((<VariableDeclarationList>node).declarations, visitor, isVariableDeclaration));
case SyntaxKind.FunctionDeclaration:
return updateFunctionDeclaration(<FunctionDeclaration>node,
visitNodes((<FunctionDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<FunctionDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<FunctionDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<FunctionDeclaration>node).modifiers, visitor, isModifier),
(<FunctionDeclaration>node).asteriskToken,
visitNode((<FunctionDeclaration>node).name, visitor, isIdentifier),
visitNodes((<FunctionDeclaration>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<FunctionDeclaration>node).parameters, visitor, context),
nodesVisitor((<FunctionDeclaration>node).typeParameters, visitor, isTypeParameter),
visitParameterList((<FunctionDeclaration>node).parameters, visitor, context, nodesVisitor),
visitNode((<FunctionDeclaration>node).type, visitor, isTypeNode),
visitFunctionBody((<FunctionExpression>node).body, visitor, context));
case SyntaxKind.ClassDeclaration:
return updateClassDeclaration(<ClassDeclaration>node,
visitNodes((<ClassDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ClassDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<ClassDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ClassDeclaration>node).modifiers, visitor, isModifier),
visitNode((<ClassDeclaration>node).name, visitor, isIdentifier),
visitNodes((<ClassDeclaration>node).typeParameters, visitor, isTypeParameter),
visitNodes((<ClassDeclaration>node).heritageClauses, visitor, isHeritageClause),
visitNodes((<ClassDeclaration>node).members, visitor, isClassElement));
nodesVisitor((<ClassDeclaration>node).typeParameters, visitor, isTypeParameter),
nodesVisitor((<ClassDeclaration>node).heritageClauses, visitor, isHeritageClause),
nodesVisitor((<ClassDeclaration>node).members, visitor, isClassElement));
case SyntaxKind.EnumDeclaration:
return updateEnumDeclaration(<EnumDeclaration>node,
visitNodes((<EnumDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<EnumDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<EnumDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<EnumDeclaration>node).modifiers, visitor, isModifier),
visitNode((<EnumDeclaration>node).name, visitor, isIdentifier),
visitNodes((<EnumDeclaration>node).members, visitor, isEnumMember));
nodesVisitor((<EnumDeclaration>node).members, visitor, isEnumMember));
case SyntaxKind.ModuleDeclaration:
return updateModuleDeclaration(<ModuleDeclaration>node,
visitNodes((<ModuleDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ModuleDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<ModuleDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ModuleDeclaration>node).modifiers, visitor, isModifier),
visitNode((<ModuleDeclaration>node).name, visitor, isIdentifier),
visitNode((<ModuleDeclaration>node).body, visitor, isModuleBody));
case SyntaxKind.ModuleBlock:
return updateModuleBlock(<ModuleBlock>node,
visitNodes((<ModuleBlock>node).statements, visitor, isStatement));
nodesVisitor((<ModuleBlock>node).statements, visitor, isStatement));
case SyntaxKind.CaseBlock:
return updateCaseBlock(<CaseBlock>node,
visitNodes((<CaseBlock>node).clauses, visitor, isCaseOrDefaultClause));
nodesVisitor((<CaseBlock>node).clauses, visitor, isCaseOrDefaultClause));
case SyntaxKind.ImportEqualsDeclaration:
return updateImportEqualsDeclaration(<ImportEqualsDeclaration>node,
visitNodes((<ImportEqualsDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ImportEqualsDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<ImportEqualsDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ImportEqualsDeclaration>node).modifiers, visitor, isModifier),
visitNode((<ImportEqualsDeclaration>node).name, visitor, isIdentifier),
visitNode((<ImportEqualsDeclaration>node).moduleReference, visitor, isModuleReference));
case SyntaxKind.ImportDeclaration:
return updateImportDeclaration(<ImportDeclaration>node,
visitNodes((<ImportDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ImportDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<ImportDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ImportDeclaration>node).modifiers, visitor, isModifier),
visitNode((<ImportDeclaration>node).importClause, visitor, isImportClause),
visitNode((<ImportDeclaration>node).moduleSpecifier, visitor, isExpression));
@ -618,7 +618,7 @@ namespace ts {
case SyntaxKind.NamedImports:
return updateNamedImports(<NamedImports>node,
visitNodes((<NamedImports>node).elements, visitor, isImportSpecifier));
nodesVisitor((<NamedImports>node).elements, visitor, isImportSpecifier));
case SyntaxKind.ImportSpecifier:
return updateImportSpecifier(<ImportSpecifier>node,
@ -627,20 +627,20 @@ namespace ts {
case SyntaxKind.ExportAssignment:
return updateExportAssignment(<ExportAssignment>node,
visitNodes((<ExportAssignment>node).decorators, visitor, isDecorator),
visitNodes((<ExportAssignment>node).modifiers, visitor, isModifier),
nodesVisitor((<ExportAssignment>node).decorators, visitor, isDecorator),
nodesVisitor((<ExportAssignment>node).modifiers, visitor, isModifier),
visitNode((<ExportAssignment>node).expression, visitor, isExpression));
case SyntaxKind.ExportDeclaration:
return updateExportDeclaration(<ExportDeclaration>node,
visitNodes((<ExportDeclaration>node).decorators, visitor, isDecorator),
visitNodes((<ExportDeclaration>node).modifiers, visitor, isModifier),
nodesVisitor((<ExportDeclaration>node).decorators, visitor, isDecorator),
nodesVisitor((<ExportDeclaration>node).modifiers, visitor, isModifier),
visitNode((<ExportDeclaration>node).exportClause, visitor, isNamedExports),
visitNode((<ExportDeclaration>node).moduleSpecifier, visitor, isExpression));
case SyntaxKind.NamedExports:
return updateNamedExports(<NamedExports>node,
visitNodes((<NamedExports>node).elements, visitor, isExportSpecifier));
nodesVisitor((<NamedExports>node).elements, visitor, isExportSpecifier));
case SyntaxKind.ExportSpecifier:
return updateExportSpecifier(<ExportSpecifier>node,
@ -656,12 +656,12 @@ namespace ts {
case SyntaxKind.JsxElement:
return updateJsxElement(<JsxElement>node,
visitNode((<JsxElement>node).openingElement, visitor, isJsxOpeningElement),
visitNodes((<JsxElement>node).children, visitor, isJsxChild),
nodesVisitor((<JsxElement>node).children, visitor, isJsxChild),
visitNode((<JsxElement>node).closingElement, visitor, isJsxClosingElement));
case SyntaxKind.JsxAttributes:
return updateJsxAttributes(<JsxAttributes>node,
visitNodes((<JsxAttributes>node).properties, visitor, isJsxAttributeLike));
nodesVisitor((<JsxAttributes>node).properties, visitor, isJsxAttributeLike));
case SyntaxKind.JsxSelfClosingElement:
return updateJsxSelfClosingElement(<JsxSelfClosingElement>node,
@ -694,15 +694,15 @@ namespace ts {
case SyntaxKind.CaseClause:
return updateCaseClause(<CaseClause>node,
visitNode((<CaseClause>node).expression, visitor, isExpression),
visitNodes((<CaseClause>node).statements, visitor, isStatement));
nodesVisitor((<CaseClause>node).statements, visitor, isStatement));
case SyntaxKind.DefaultClause:
return updateDefaultClause(<DefaultClause>node,
visitNodes((<DefaultClause>node).statements, visitor, isStatement));
nodesVisitor((<DefaultClause>node).statements, visitor, isStatement));
case SyntaxKind.HeritageClause:
return updateHeritageClause(<HeritageClause>node,
visitNodes((<HeritageClause>node).types, visitor, isExpressionWithTypeArguments));
nodesVisitor((<HeritageClause>node).types, visitor, isExpressionWithTypeArguments));
case SyntaxKind.CatchClause:
return updateCatchClause(<CatchClause>node,

View File

@ -22,6 +22,10 @@
namespace FourSlash {
ts.disableIncrementalParsing = false;
function normalizeNewLines(s: string) {
return s.replace(/\r\n/g, "\n");
}
// Represents a parsed source file with metadata
export interface FourSlashFile {
// The contents of the file (with markers, etc stripped out)
@ -1958,8 +1962,7 @@ namespace FourSlash {
public verifyCurrentFileContent(text: string) {
const actual = this.getFileContent(this.activeFile.fileName);
const replaceNewlines = (str: string) => str.replace(/\r\n/g, "\n");
if (replaceNewlines(actual) !== replaceNewlines(text)) {
if (normalizeNewLines(actual) !== normalizeNewLines(text)) {
throw new Error("verifyCurrentFileContent\n" +
"\tExpected: \"" + text + "\"\n" +
"\t Actual: \"" + actual + "\"");
@ -2135,7 +2138,7 @@ namespace FourSlash {
const actualText = this.rangeText(ranges[0]);
const result = includeWhiteSpace
? actualText === expectedText
? normalizeNewLines(actualText) === normalizeNewLines(expectedText)
: this.removeWhitespace(actualText) === this.removeWhitespace(expectedText);
if (!result) {
@ -2185,7 +2188,7 @@ namespace FourSlash {
continue;
}
const newActions = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code]);
const newActions = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code], this.formatCodeSettings);
if (newActions && newActions.length) {
actions = actions ? actions.concat(newActions) : newActions;
}

View File

@ -125,6 +125,7 @@
"./unittests/projectErrors.ts",
"./unittests/printer.ts",
"./unittests/transform.ts",
"./unittests/customTransforms.ts"
"./unittests/customTransforms.ts",
"./unittests/textChanges.ts"
]
}

View File

@ -0,0 +1,791 @@
/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\..\services\textChanges.ts" />
/// <reference path="..\harness.ts" />
namespace ts {
describe("textChanges", () => {
function findChild(name: string, n: Node) {
return find(n);
function find(node: Node): Node {
if (isDeclaration(node) && node.name && isIdentifier(node.name) && node.name.text === name) {
return node;
}
else {
return forEachChild(node, find);
}
}
}
const printerOptions = { newLine: NewLineKind.LineFeed };
const newLineCharacter = getNewLineCharacter(printerOptions);
function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
const options = {
indentSize: 4,
tabSize: 4,
newLineCharacter,
convertTabsToSpaces: true,
indentStyle: ts.IndentStyle.Smart,
insertSpaceAfterConstructor: false,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
};
if (action) {
action(options);
}
const rulesProvider = new formatting.RulesProvider();
rulesProvider.ensureUpToDate(options);
return rulesProvider;
}
// validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed.
function verifyPositions({ text, node }: textChanges.NonFormattedText): void {
const nodeList = flattenNodes(node);
const sourceFile = createSourceFile("f.ts", text, ScriptTarget.ES2015);
const parsedNodeList = flattenNodes(sourceFile.statements[0]);
Debug.assert(nodeList.length === parsedNodeList.length);
for (let i = 0; i < nodeList.length; i++) {
const left = nodeList[i];
const right = parsedNodeList[i];
Debug.assert(left.pos === right.pos);
Debug.assert(left.end === right.end);
}
function flattenNodes(n: Node) {
const data: (Node | NodeArray<any>)[] = [];
walk(n);
return data;
function walk(n: Node | Node[]): void {
data.push(<any>n);
return isArray(n) ? forEach(n, walk) : forEachChild(n, walk, walk);
}
}
}
function runSingleFileTest(caption: string, setupFormatOptions: (opts: FormatCodeSettings) => void, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) {
it(caption, () => {
Harness.Baseline.runBaseline(`textChanges/${caption}.js`, () => {
const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true);
const rulesProvider = getRuleProvider(setupFormatOptions);
const changeTracker = new textChanges.ChangeTracker(printerOptions.newLine, rulesProvider, validateNodes ? verifyPositions : undefined);
testBlock(sourceFile, changeTracker);
const changes = changeTracker.getChanges();
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, sourceFile.fileName);
const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
return `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`;
});
});
}
function setNewLineForOpenBraceInFunctions(opts: FormatCodeSettings) {
opts.placeOpenBraceOnNewLineForFunctions = true;
}
{
const text = `
namespace M
{
namespace M2
{
function foo() {
// comment 1
const x = 1;
/**
* comment 2 line 1
* comment 2 line 2
*/
function f() {
return 100;
}
const y = 2; // comment 3
return 1;
}
}
}`;
runSingleFileTest("extractMethodLike", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
const statements = (<Block>(<FunctionDeclaration>findChild("foo", sourceFile)).body).statements.slice(1);
const newFunction = createFunctionDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ "bar",
/*typeParameters*/ undefined,
/*parameters*/ emptyArray,
/*type*/ createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*body */ createBlock(statements)
);
changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { suffix: newLineCharacter });
// replace statements with return statement
const newStatement = createReturn(
createCall(
/*expression*/ newFunction.name,
/*typeArguments*/ undefined,
/*argumentsArray*/ emptyArray
));
changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { suffix: newLineCharacter });
});
}
{
const text = `
function foo() {
return 1;
}
function bar() {
return 2;
}
`;
runSingleFileTest("deleteRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") });
});
}
function findVariableStatementContaining(name: string, sourceFile: SourceFile) {
const varDecl = findChild(name, sourceFile);
assert.equal(varDecl.kind, SyntaxKind.VariableDeclaration);
const varStatement = varDecl.parent.parent;
assert.equal(varStatement.kind, SyntaxKind.VariableStatement);
return varStatement;
}
{
const text = `
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
`;
runSingleFileTest("deleteNode1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile));
});
runSingleFileTest("deleteNode2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNode3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("x", sourceFile));
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
`;
runSingleFileTest("deleteNodeRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile));
});
runSingleFileTest("deleteNodeRange2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNodeRange3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNodeRange4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
function createTestVariableDeclaration(name: string) {
return createVariableDeclaration(name, /*type*/ undefined, createObjectLiteral([createPropertyAssignment("p1", createLiteral(1))], /*multiline*/ true));
}
function createTestClass() {
return createClassDeclaration(
/*decorators*/ undefined,
[
createToken(SyntaxKind.PublicKeyword)
],
"class1",
/*typeParameters*/ undefined,
[
createHeritageClause(
SyntaxKind.ImplementsKeyword,
[
createExpressionWithTypeArguments(/*typeArguments*/ undefined, createIdentifier("interface1"))
]
)
],
[
createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
"property1",
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.BooleanKeyword),
/*initializer*/ undefined
)
]
);
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceRange", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceRangeWithForcedIndentation", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 });
});
runSingleFileTest("replaceRangeNoLineBreakBefore", setNewLineForOpenBraceInFunctions, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createTestVariableDeclaration("z1");
changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode);
});
}
{
const text = `
namespace A {
const x = 1, y = "2";
}
`;
runSingleFileTest("replaceNode1NoLineBreakBefore", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createTestVariableDeclaration("z1");
changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode);
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceNode1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceNode2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
});
runSingleFileTest("replaceNode3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
});
runSingleFileTest("replaceNode4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("replaceNode5", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceNodeRange1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("insertNodeAt1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAt2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1"));
});
}
{
const text = `
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}`;
runSingleFileTest("insertNodeBefore1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeBefore2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAfter1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAfter2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { prefix: newLineCharacter });
});
}
{
function findOpenBraceForConstructor(sourceFile: SourceFile) {
const classDecl = <ClassDeclaration>sourceFile.statements[0];
const constructorDecl = forEach(classDecl.members, m => m.kind === SyntaxKind.Constructor && (<ConstructorDeclaration>m).body && <ConstructorDeclaration>m);
return constructorDecl.body.getFirstToken();
}
function createTestSuperCall() {
const superCall = createCall(
createSuper(),
/*typeArguments*/ undefined,
/*argumentsArray*/ emptyArray
);
return createStatement(superCall);
}
const text1 = `
class A {
constructor() {
}
}
`;
runSingleFileTest("insertNodeAfter3", noop, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
const text2 = `
class A {
constructor() {
var x = 1;
}
}
`;
runSingleFileTest("insertNodeAfter4", noop, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
const text3 = `
class A {
constructor() {
}
}
`;
runSingleFileTest("insertNodeAfter3-block with newline", noop, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
}
{
const text = `var a = 1, b = 2, c = 3;`;
runSingleFileTest("deleteNodeInList1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `var a = 1,b = 2,c = 3;`;
runSingleFileTest("deleteNodeInList1_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
namespace M {
var a = 1,
b = 2,
c = 3;
}`;
runSingleFileTest("deleteNodeInList4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
namespace M {
var a = 1, // comment 1
// comment 2
b = 2, // comment 3
// comment 4
c = 3; // comment 5
}`;
runSingleFileTest("deleteNodeInList4_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
function foo(a: number, b: string, c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
function foo(a: number,b: string,c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
function foo(
a: number,
b: string,
c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
const x = 1, y = 2;`;
runSingleFileTest("insertNodeInListAfter1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const /*x*/ x = 1, /*y*/ y = 2;`;
runSingleFileTest("insertNodeInListAfter3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const x = 1;`;
runSingleFileTest("insertNodeInListAfter5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const x = 1,
y = 2;`;
runSingleFileTest("insertNodeInListAfter6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const /*x*/ x = 1,
/*y*/ y = 2;`;
runSingleFileTest("insertNodeInListAfter8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
})
}
{
const text = `
import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
})
}
{
const text = `
import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
})
}
{
const text = `
import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
})
}
{
const text = `
import {
x0,
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
})
}
{
const text = `
import {
x0,
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
})
}
{
const text = `
import {
x0,
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter16", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
})
}
{
const text = `
import {
x0,
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter17", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
})
}
{
const text = `
import {
x0, x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter18", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
})
}
{
const text = `
class A {
x;
}`;
runSingleFileTest("insertNodeAfterMultipleNodes", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
let newNodes = [];
for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) {
newNodes.push(
createProperty(undefined, undefined, i + "", undefined, undefined, undefined));
}
const insertAfter = findChild("x", sourceFile);
for (const newNode of newNodes) {
changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: newLineCharacter });
}
});
}
{
const text = `
class A {
x
}
`;
runSingleFileTest("insertNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter });
});
}
{
const text = `
class A {
x;
}
`;
runSingleFileTest("insertNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter });
});
}
{
const text = `
class A {
x;
y = 1;
}
`;
runSingleFileTest("deleteNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
});
}
{
const text = `
class A {
x
y = 1;
}
`;
runSingleFileTest("deleteNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
});
}
{
const text = `
class A {
x = foo
}
`
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
class A {
x() {
}
}
`
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
interface A {
x
}
`
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
interface A {
x()
}
`
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
let x = foo
`
runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createStatement(createParen(createLiteral(1)));
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
});
}

View File

@ -1420,8 +1420,9 @@ namespace ts.server {
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const startPosition = getStartPosition();
const endPosition = getEndPosition();
const formatOptions = this.projectService.getFormatCodeOptions(file);
const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes);
const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, formatOptions);
if (!codeActions) {
return undefined;
}

View File

@ -13,6 +13,7 @@ namespace ts {
newLineCharacter: string;
host: LanguageServiceHost;
cancellationToken: CancellationToken;
rulesProvider: formatting.RulesProvider;
}
export namespace codefix {

View File

@ -26,22 +26,13 @@ namespace ts.codefix {
}
}
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>constructor, sourceFile);
const changes = [{
fileName: sourceFile.fileName, textChanges: [{
newText: superCall.getText(sourceFile),
span: { start: newPosition, length: 0 }
},
{
newText: "",
span: { start: superCall.getStart(sourceFile), length: superCall.getWidth(sourceFile) }
}]
}];
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>constructor, sourceFile), superCall, { suffix: context.newLineCharacter });
changeTracker.deleteNode(sourceFile, superCall);
return [{
description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor),
changes
changes: changeTracker.getChanges()
}];
function findSuperCall(n: Node): ExpressionStatement {

View File

@ -10,10 +10,13 @@ namespace ts.codefix {
return undefined;
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>token.parent, sourceFile);
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray));
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>token.parent, sourceFile), superCall, { suffix: context.newLineCharacter });
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }]
changes: changeTracker.getChanges()
}];
}
});

View File

@ -21,26 +21,20 @@ namespace ts.codefix {
return undefined;
}
let changeStart = extendsToken.getStart(sourceFile);
let changeEnd = extendsToken.getEnd();
const textChanges: TextChange[] = [{ newText: " implements", span: { start: changeStart, length: changeEnd - changeStart } }];
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
changeTracker.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
// We replace existing keywords with commas.
for (let i = 1; i < heritageClauses.length; i++) {
const keywordToken = heritageClauses[i].getFirstToken();
if (keywordToken) {
changeStart = keywordToken.getStart(sourceFile);
changeEnd = keywordToken.getEnd();
textChanges.push({ newText: ",", span: { start: changeStart, length: changeEnd - changeStart } });
changeTracker.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken));
}
}
const result = [{
description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
}]
changes: changeTracker.getChanges()
}];
return result;

View File

@ -5,11 +5,15 @@ namespace ts.codefix {
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
const start = token.getStart(sourceFile);
if (token.kind !== SyntaxKind.Identifier) {
return undefined;
}
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context);
changeTracker.replaceNode(sourceFile, token, createPropertyAccess(createThis(), <Identifier>token));
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "this.", span: { start, length: 0 } }] }]
changes: changeTracker.getChanges()
}];
}
});

View File

@ -130,7 +130,7 @@ namespace ts.codefix {
// this is a module id -> module import declaration map
const cachedImportDeclarations: (ImportDeclaration | ImportEqualsDeclaration)[][] = [];
let cachedNewImportInsertPosition: number;
let lastImportDeclaration: Node;
const currentTokenMeaning = getMeaningFromLocation(token);
if (context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code) {
@ -138,14 +138,14 @@ namespace ts.codefix {
return getCodeActionForImport(symbol, /*isDefault*/ false, /*isNamespaceImport*/ true);
}
const allPotentialModules = checker.getAmbientModules();
const candidateModules = checker.getAmbientModules();
for (const otherSourceFile of allSourceFiles) {
if (otherSourceFile !== sourceFile && isExternalOrCommonJsModule(otherSourceFile)) {
allPotentialModules.push(otherSourceFile.symbol);
candidateModules.push(otherSourceFile.symbol);
}
}
for (const moduleSymbol of allPotentialModules) {
for (const moduleSymbol of candidateModules) {
context.cancellationToken.throwIfCancellationRequested();
// check the default export
@ -277,14 +277,12 @@ namespace ts.codefix {
* If the existing import declaration already has a named import list, just
* insert the identifier into that list.
*/
const textChange = getTextChangeForImportClause(namedImportDeclaration.importClause);
const fileTextChanges = getTextChangeForImportClause(namedImportDeclaration.importClause);
const moduleSpecifierWithoutQuotes = stripQuotes(namedImportDeclaration.moduleSpecifier.getText());
actions.push(createCodeAction(
Diagnostics.Add_0_to_existing_import_declaration_from_1,
[name, moduleSpecifierWithoutQuotes],
textChange.newText,
textChange.span,
sourceFile.fileName,
fileTextChanges,
"InsertingIntoExistingImport",
moduleSpecifierWithoutQuotes
));
@ -302,49 +300,31 @@ namespace ts.codefix {
return declaration.moduleReference.getText();
}
function getTextChangeForImportClause(importClause: ImportClause): TextChange {
const newImportText = isDefault ? `default as ${name}` : name;
function getTextChangeForImportClause(importClause: ImportClause): FileTextChanges[] {
//const newImportText = isDefault ? `default as ${name}` : name;
const importList = <NamedImports>importClause.namedBindings;
const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name));
// case 1:
// original text: import default from "module"
// change to: import default, { name } from "module"
if (!importList && importClause.name) {
const start = importClause.name.getEnd();
return {
newText: `, { ${newImportText} }`,
span: { start, length: 0 }
};
}
// case 2:
// original text: import {} from "module"
// change to: import { name } from "module"
if (importList.elements.length === 0) {
const start = importList.getStart();
return {
newText: `{ ${newImportText} }`,
span: { start, length: importList.getEnd() - start }
};
if (!importList || importList.elements.length === 0) {
const newImportClause = createImportClause(importClause.name, createNamedImports([newImportSpecifier]));
return createChangeTracker().replaceNode(sourceFile, importClause, newImportClause).getChanges();
}
// case 3:
// original text: import { foo, bar } from "module"
// change to: import { foo, bar, name } from "module"
const insertPoint = importList.elements[importList.elements.length - 1].getEnd();
/**
* If the import list has one import per line, preserve that. Otherwise, insert on same line as last element
* import {
* foo
* } from "./module";
*/
const startLine = getLineOfLocalPosition(sourceFile, importList.getStart());
const endLine = getLineOfLocalPosition(sourceFile, importList.getEnd());
const oneImportPerLine = endLine - startLine > importList.elements.length;
return {
newText: `,${oneImportPerLine ? context.newLineCharacter : " "}${newImportText}`,
span: { start: insertPoint, length: 0 }
};
return createChangeTracker().insertNodeInListAfter(
sourceFile,
importList.elements[importList.elements.length - 1],
newImportSpecifier).getChanges();
}
function getCodeActionForNamespaceImport(declaration: ImportDeclaration | ImportEqualsDeclaration): ImportCodeAction {
@ -370,48 +350,47 @@ namespace ts.codefix {
return createCodeAction(
Diagnostics.Change_0_to_1,
[name, `${namespacePrefix}.${name}`],
`${namespacePrefix}.`,
{ start: token.getStart(), length: 0 },
sourceFile.fileName,
createChangeTracker().replaceNode(sourceFile, token, createPropertyAccess(createIdentifier(namespacePrefix), name)).getChanges(),
"CodeChange"
);
}
}
function getCodeActionForNewImport(moduleSpecifier?: string): ImportCodeAction {
if (!cachedNewImportInsertPosition) {
if (!lastImportDeclaration) {
// insert after any existing imports
let lastModuleSpecifierEnd = -1;
for (const moduleSpecifier of sourceFile.imports) {
const end = moduleSpecifier.getEnd();
if (!lastModuleSpecifierEnd || end > lastModuleSpecifierEnd) {
lastModuleSpecifierEnd = end;
for (let i = sourceFile.statements.length - 1; i >= 0; i--) {
const statement = sourceFile.statements[i];
if (statement.kind === SyntaxKind.ImportEqualsDeclaration || statement.kind === SyntaxKind.ImportDeclaration) {
lastImportDeclaration = statement;
break;
}
}
cachedNewImportInsertPosition = lastModuleSpecifierEnd > 0 ? sourceFile.getLineEndOfPosition(lastModuleSpecifierEnd) : sourceFile.getStart();
}
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier || getModuleSpecifierForNewImport());
const importStatementText = isDefault
? `import ${name} from "${moduleSpecifierWithoutQuotes}"`
const changeTracker = createChangeTracker();
const importClause = isDefault
? createImportClause(createIdentifier(name), /*namedBindings*/ undefined)
: isNamespaceImport
? `import * as ${name} from "${moduleSpecifierWithoutQuotes}"`
: `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`;
? createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(name)))
: createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))]));
const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifierWithoutQuotes));
if (!lastImportDeclaration) {
changeTracker.insertNodeAt(sourceFile, sourceFile.getStart(), importDecl, { suffix: `${context.newLineCharacter}${context.newLineCharacter}` });
}
else {
changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: context.newLineCharacter });
}
// if this file doesn't have any import statements, insert an import statement and then insert a new line
// between the only import statement and user code. Otherwise just insert the statement because chances
// are there are already a new line seperating code and import statements.
const newText = cachedNewImportInsertPosition === sourceFile.getStart()
? `${importStatementText};${context.newLineCharacter}${context.newLineCharacter}`
: `${context.newLineCharacter}${importStatementText};`;
return createCodeAction(
Diagnostics.Import_0_from_1,
[name, `"${moduleSpecifierWithoutQuotes}"`],
newText,
{ start: cachedNewImportInsertPosition, length: 0 },
sourceFile.fileName,
changeTracker.getChanges(),
"NewImport",
moduleSpecifierWithoutQuotes
);
@ -576,17 +555,19 @@ namespace ts.codefix {
}
function createChangeTracker() {
return textChanges.ChangeTracker.fromCodeFixContext(context);;
}
function createCodeAction(
description: DiagnosticMessage,
diagnosticArgs: string[],
newText: string,
span: TextSpan,
fileName: string,
changes: FileTextChanges[],
kind: ImportCodeActionKind,
moduleSpecifier?: string): ImportCodeAction {
return {
description: formatMessage.apply(undefined, [undefined, description].concat(<any[]>diagnosticArgs)),
changes: [{ fileName, textChanges: [{ newText, span }] }],
changes,
kind,
moduleSpecifier
};

View File

@ -25,17 +25,17 @@ namespace ts.codefix {
const forStatement = <ForStatement>token.parent.parent.parent;
const forInitializer = <VariableDeclarationList>forStatement.initializer;
if (forInitializer.declarations.length === 1) {
return createCodeFixToRemoveNode(forInitializer);
return deleteNode(forInitializer);
}
else {
return removeSingleItem(forInitializer.declarations, token);
return deleteNodeInList(token.parent);
}
case SyntaxKind.ForOfStatement:
const forOfStatement = <ForOfStatement>token.parent.parent.parent;
if (forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) {
const forOfInitializer = <VariableDeclarationList>forOfStatement.initializer;
return createCodeFix("{}", forOfInitializer.declarations[0].getStart(), forOfInitializer.declarations[0].getWidth());
return replaceNode(forOfInitializer.declarations[0], createObjectLiteral());
}
break;
@ -47,51 +47,59 @@ namespace ts.codefix {
case SyntaxKind.CatchClause:
const catchClause = <CatchClause>token.parent.parent;
const parameter = catchClause.variableDeclaration.getChildren()[0];
return createCodeFixToRemoveNode(parameter);
return deleteNode(parameter);
default:
const variableStatement = <VariableStatement>token.parent.parent.parent;
if (variableStatement.declarationList.declarations.length === 1) {
return createCodeFixToRemoveNode(variableStatement);
return deleteNode(variableStatement);
}
else {
const declarations = variableStatement.declarationList.declarations;
return removeSingleItem(declarations, token);
return deleteNodeInList(token.parent);
}
}
case SyntaxKind.TypeParameter:
const typeParameters = (<DeclarationWithTypeParameters>token.parent.parent).typeParameters;
if (typeParameters.length === 1) {
return createCodeFix("", token.parent.pos - 1, token.parent.end - token.parent.pos + 2);
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1);
if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) {
return deleteRange(typeParameters);
}
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end);
if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) {
return deleteRange(typeParameters);
}
return deleteNodeRange(previousToken, nextToken);
}
else {
return removeSingleItem(typeParameters, token);
return deleteNodeInList(token.parent);
}
case ts.SyntaxKind.Parameter:
const functionDeclaration = <FunctionDeclaration>token.parent.parent;
if (functionDeclaration.parameters.length === 1) {
return createCodeFixToRemoveNode(token.parent);
return deleteNode(token.parent);
}
else {
return removeSingleItem(functionDeclaration.parameters, token);
return deleteNodeInList(token.parent);
}
// handle case where 'import a = A;'
case SyntaxKind.ImportEqualsDeclaration:
const importEquals = findImportDeclaration(token);
return createCodeFixToRemoveNode(importEquals);
const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration);
return deleteNode(importEquals);
case SyntaxKind.ImportSpecifier:
const namedImports = <NamedImports>token.parent.parent;
if (namedImports.elements.length === 1) {
// Only 1 import and it is unused. So the entire declaration should be removed.
const importSpec = findImportDeclaration(token);
return createCodeFixToRemoveNode(importSpec);
const importSpec = getAncestor(token, SyntaxKind.ImportDeclaration);
return deleteNode(importSpec);
}
else {
return removeSingleItem(namedImports.elements, token);
// delete import specifier
return deleteNodeInList(token.parent);
}
// handle case where "import d, * as ns from './file'"
@ -99,98 +107,79 @@ namespace ts.codefix {
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
const importClause = <ImportClause>token.parent;
if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'|
const importDecl = findImportDeclaration(importClause);
return createCodeFixToRemoveNode(importDecl);
const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration);
return deleteNode(importDecl);
}
else {
// import |d,| * as ns from './file'
const start = importClause.name.getStart();
let end = findFirstNonSpaceCharPosStarting(importClause.name.end);
if (sourceFile.text.charCodeAt(end) === CharacterCodes.comma) {
end = findFirstNonSpaceCharPosStarting(end + 1);
const start = importClause.name.getStart(sourceFile);
const nextToken = getTokenAtPosition(sourceFile, importClause.name.end);
if (nextToken && nextToken.kind === SyntaxKind.CommaToken) {
// shift first non-whitespace position after comma to the start position of the node
return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/true) });
}
else {
return deleteNode(importClause.name);
}
return createCodeFix("", start, end - start);
}
case SyntaxKind.NamespaceImport:
const namespaceImport = <NamespaceImport>token.parent;
if (namespaceImport.name == token && !(<ImportClause>namespaceImport.parent).name) {
const importDecl = findImportDeclaration(namespaceImport);
return createCodeFixToRemoveNode(importDecl);
const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration);
return deleteNode(importDecl);
}
else {
const start = (<ImportClause>namespaceImport.parent).name.end;
return createCodeFix("", start, (<ImportClause>namespaceImport.parent).namedBindings.end - start);
const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1);
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart);
return deleteRange({ pos: startPosition, end: namespaceImport.end });
}
return deleteRange(namespaceImport);
}
}
break;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.NamespaceImport:
return createCodeFixToRemoveNode(token.parent);
return deleteNode(token.parent);
}
if (isDeclarationName(token)) {
return createCodeFixToRemoveNode(token.parent);
return deleteNode(token.parent);
}
else if (isLiteralComputedPropertyDeclarationName(token)) {
return createCodeFixToRemoveNode(token.parent.parent);
return deleteNode(token.parent.parent);
}
else {
return undefined;
}
function findImportDeclaration(token: Node): Node {
let importDecl = token;
while (importDecl.kind != SyntaxKind.ImportDeclaration && importDecl.parent) {
importDecl = importDecl.parent;
}
return importDecl;
function deleteNode(n: Node) {
return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteNode(sourceFile, n));
}
function createCodeFixToRemoveNode(node: Node) {
let end = node.getEnd();
const endCharCode = sourceFile.text.charCodeAt(end);
const afterEndCharCode = sourceFile.text.charCodeAt(end + 1);
if (isLineBreak(endCharCode)) {
end += 1;
}
// in the case of CR LF, you could have two consecutive new line characters for one new line.
// this needs to be differenciated from two LF LF chars that actually mean two new lines.
if (isLineBreak(afterEndCharCode) && endCharCode !== afterEndCharCode) {
end += 1;
}
const start = node.getStart();
return createCodeFix("", start, end - start);
function deleteRange(range: TextRange) {
return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteRange(sourceFile, range));
}
function findFirstNonSpaceCharPosStarting(start: number) {
while (isWhiteSpace(sourceFile.text.charCodeAt(start))) {
start += 1;
}
return start;
function deleteNodeInList(n: Node) {
return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteNodeInList(sourceFile, n));
}
function createCodeFix(newText: string, start: number, length: number): CodeAction[] {
function deleteNodeRange(start: Node, end: Node) {
return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteNodeRange(sourceFile, start, end));
}
function replaceNode(n: Node, newNode: Node) {
return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).replaceNode(sourceFile, n, newNode));
}
function makeChange(changeTracker: textChanges.ChangeTracker) {
return [{
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), { 0: token.getText() }),
changes: [{
fileName: sourceFile.fileName,
textChanges: [{ newText, span: { start, length } }]
}]
changes: changeTracker.getChanges()
}];
}
function removeSingleItem<T extends Node>(elements: NodeArray<T>, token: T): CodeAction[] {
if (elements[0] === token.parent) {
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos + 1);
}
else {
return createCodeFix("", token.parent.pos - 1, token.parent.end - token.parent.pos + 1);
}
}
}
});
}

View File

@ -314,24 +314,55 @@ namespace ts.formatting {
return 0;
}
/* @internal */
export function formatNode(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, rulesProvider: RulesProvider): TextChange[] {
const range = { pos: 0, end: sourceFileLike.text.length };
return formatSpanWorker(
range,
node,
initialIndentation,
delta,
getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end),
rulesProvider.getFormatOptions(),
rulesProvider,
FormattingRequestKind.FormatSelection,
_ => false, // assume that node does not have any errors
sourceFileLike);
}
function formatSpan(originalRange: TextRange,
sourceFile: SourceFile,
options: FormatCodeSettings,
rulesProvider: RulesProvider,
requestKind: FormattingRequestKind): TextChange[] {
// find the smallest node that fully wraps the range and compute the initial indentation for the node
const enclosingNode = findEnclosingNode(originalRange, sourceFile);
return formatSpanWorker(
originalRange,
enclosingNode,
SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, options),
getOwnOrInheritedDelta(enclosingNode, options, sourceFile),
getFormattingScanner(sourceFile.text, sourceFile.languageVariant, getScanStartPosition(enclosingNode, originalRange, sourceFile), originalRange.end),
options,
rulesProvider,
requestKind,
prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange),
sourceFile);
}
const rangeContainsError = prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange);
function formatSpanWorker(originalRange: TextRange,
enclosingNode: Node,
initialIndentation: number,
delta: number,
formattingScanner: FormattingScanner,
options: FormatCodeSettings,
rulesProvider: RulesProvider,
requestKind: FormattingRequestKind,
rangeContainsError: (r: TextRange) => boolean,
sourceFile: SourceFileLike): TextChange[] {
// formatting context is used by rules provider
const formattingContext = new FormattingContext(sourceFile, requestKind);
// find the smallest node that fully wraps the range and compute the initial indentation for the node
const enclosingNode = findEnclosingNode(originalRange, sourceFile);
const formattingScanner = getFormattingScanner(sourceFile, getScanStartPosition(enclosingNode, originalRange, sourceFile), originalRange.end);
const initialIndentation = SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, options);
let previousRangeHasError: boolean;
let previousRange: TextRangeWithKind;
let previousParent: Node;
@ -351,7 +382,6 @@ namespace ts.formatting {
undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line;
}
const delta = getOwnOrInheritedDelta(enclosingNode, options, sourceFile);
processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta);
}

View File

@ -15,7 +15,7 @@ namespace ts.formatting {
private contextNodeBlockIsOnOneLine: boolean;
private nextNodeBlockIsOnOneLine: boolean;
constructor(public sourceFile: SourceFile, public formattingRequestKind: FormattingRequestKind) {
constructor(public readonly sourceFile: SourceFileLike, public formattingRequestKind: FormattingRequestKind) {
}
public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) {

View File

@ -30,11 +30,11 @@ namespace ts.formatting {
RescanJsxText,
}
export function getFormattingScanner(sourceFile: SourceFile, startPos: number, endPos: number): FormattingScanner {
export function getFormattingScanner(text: string, languageVariant: LanguageVariant, startPos: number, endPos: number): FormattingScanner {
Debug.assert(scanner === undefined, "Scanner should be undefined");
scanner = sourceFile.languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner;
scanner = languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner;
scanner.setText(sourceFile.text);
scanner.setText(text);
scanner.setTextPos(startPos);
let wasNewLine = true;
@ -276,8 +276,8 @@ namespace ts.formatting {
function isOnToken(): boolean {
Debug.assert(scanner !== undefined);
const current = (lastTokenInfo && lastTokenInfo.token.kind) || scanner.getToken();
const startPos = (lastTokenInfo && lastTokenInfo.token.pos) || scanner.getStartPos();
const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken();
const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos();
return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current);
}

View File

@ -24,6 +24,10 @@ namespace ts.formatting {
return this.rulesMap;
}
public getFormatOptions(): Readonly<ts.FormatCodeSettings> {
return this.options;
}
public ensureUpToDate(options: ts.FormatCodeSettings) {
if (!this.options || !ts.compareDataObjects(this.options, options)) {
const activeRules = this.createActiveRules(options);

View File

@ -8,7 +8,19 @@ namespace ts.formatting {
Unknown = -1
}
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings): number {
/**
* Computed indentation for a given position in source file
* @param position - position in file
* @param sourceFile - target source file
* @param options - set of editor options that control indentation
* @param assumeNewLineBeforeCloseBrace - false when getIndentation is called on the text from the real source file.
* true - when we need to assume that position is on the newline. This is usefult for codefixes, i.e.
* function f() {
* |}
* when inserting some text after open brace we would like to get the value of indentation as if newline was already there.
* However by default indentation at position | will be 0 so 'assumeNewLineBeforeCloseBrace' allows to override this behavior,
*/
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number {
if (position > sourceFile.text.length) {
return getBaseIndentation(options); // past EOF
}
@ -71,13 +83,14 @@ namespace ts.formatting {
if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous)) {
currentStart = getStartLineAndCharacterForNode(current, sourceFile);
if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {
indentationDelta = 0;
const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile);
if (nextTokenKind !== NextTokenKind.Unknown) {
// handle cases when codefix is about to be inserted before the close brace
indentationDelta = assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind.CloseBrace ? options.indentSize : 0;
}
else {
indentationDelta = lineAtPosition !== currentStart.line ? options.indentSize : 0;
}
break;
}
@ -218,15 +231,21 @@ namespace ts.formatting {
return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options);
}
function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): boolean {
const enum NextTokenKind {
Unknown,
OpenBrace,
CloseBrace
}
function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): NextTokenKind {
const nextToken = findNextToken(precedingToken, current);
if (!nextToken) {
return false;
return NextTokenKind.Unknown;
}
if (nextToken.kind === SyntaxKind.OpenBraceToken) {
// open braces are always indented at the parent level
return true;
return NextTokenKind.OpenBrace;
}
else if (nextToken.kind === SyntaxKind.CloseBraceToken) {
// close braces are indented at the parent level if they are located on the same line with cursor
@ -239,17 +258,17 @@ namespace ts.formatting {
// $}
const nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line;
return lineAtPosition === nextTokenStartLine;
return lineAtPosition === nextTokenStartLine ? NextTokenKind.CloseBrace : NextTokenKind.Unknown;
}
return false;
return NextTokenKind.Unknown;
}
function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter {
function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFileLike): LineAndCharacter {
return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));
}
export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFile): boolean {
export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean {
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile);
Debug.assert(elseKeyword !== undefined);
@ -261,15 +280,15 @@ namespace ts.formatting {
return false;
}
function getContainingList(node: Node, sourceFile: SourceFile): NodeArray<Node> {
function getListIfStartEndIsInListRange(list: NodeArray<Node>, start: number, end: number) {
return list && rangeContainsStartEnd(list, start, end) ? list : undefined;
}
export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray<Node> {
if (node.parent) {
switch (node.parent.kind) {
case SyntaxKind.TypeReference:
if ((<TypeReferenceNode>node.parent).typeArguments &&
rangeContainsStartEnd((<TypeReferenceNode>node.parent).typeArguments, node.getStart(sourceFile), node.getEnd())) {
return (<TypeReferenceNode>node.parent).typeArguments;
}
break;
return getListIfStartEndIsInListRange((<TypeReferenceNode>node.parent).typeArguments, node.getStart(sourceFile), node.getEnd());
case SyntaxKind.ObjectLiteralExpression:
return (<ObjectLiteralExpression>node.parent).properties;
case SyntaxKind.ArrayLiteralExpression:
@ -280,30 +299,26 @@ namespace ts.formatting {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.Constructor:
case SyntaxKind.ConstructorType:
case SyntaxKind.ConstructSignature: {
const start = node.getStart(sourceFile);
if ((<SignatureDeclaration>node.parent).typeParameters &&
rangeContainsStartEnd((<SignatureDeclaration>node.parent).typeParameters, start, node.getEnd())) {
return (<SignatureDeclaration>node.parent).typeParameters;
}
if (rangeContainsStartEnd((<SignatureDeclaration>node.parent).parameters, start, node.getEnd())) {
return (<SignatureDeclaration>node.parent).parameters;
}
break;
return getListIfStartEndIsInListRange((<SignatureDeclaration>node.parent).typeParameters, start, node.getEnd()) ||
getListIfStartEndIsInListRange((<SignatureDeclaration>node.parent).parameters, start, node.getEnd());
}
case SyntaxKind.ClassDeclaration:
return getListIfStartEndIsInListRange((<ClassDeclaration>node.parent).typeParameters, node.getStart(sourceFile), node.getEnd());
case SyntaxKind.NewExpression:
case SyntaxKind.CallExpression: {
const start = node.getStart(sourceFile);
if ((<CallExpression>node.parent).typeArguments &&
rangeContainsStartEnd((<CallExpression>node.parent).typeArguments, start, node.getEnd())) {
return (<CallExpression>node.parent).typeArguments;
}
if ((<CallExpression>node.parent).arguments &&
rangeContainsStartEnd((<CallExpression>node.parent).arguments, start, node.getEnd())) {
return (<CallExpression>node.parent).arguments;
}
break;
return getListIfStartEndIsInListRange((<CallExpression>node.parent).typeArguments, start, node.getEnd()) ||
getListIfStartEndIsInListRange((<CallExpression>node.parent).arguments, start, node.getEnd());
}
case SyntaxKind.VariableDeclarationList:
return getListIfStartEndIsInListRange((<VariableDeclarationList>node.parent).declarations, node.getStart(sourceFile), node.getEnd());
case SyntaxKind.NamedImports:
case SyntaxKind.NamedExports:
return getListIfStartEndIsInListRange((<NamedImportsOrExports>node.parent).elements, node.getStart(sourceFile), node.getEnd());
}
}
return undefined;
@ -400,7 +415,7 @@ namespace ts.formatting {
value of 'character' for '$' is 3
value of 'column' for '$' is 6 (assuming that tab size is 4)
*/
export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorSettings) {
export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) {
let character = 0;
let column = 0;
for (let pos = startPos; pos < endPos; pos++) {
@ -421,7 +436,7 @@ namespace ts.formatting {
return { column, character };
}
export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorSettings): number {
export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings): number {
return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column;
}

View File

@ -24,6 +24,7 @@
/// <reference path='transpile.ts' />
/// <reference path='formatting\formatting.ts' />
/// <reference path='formatting\smartIndenter.ts' />
/// <reference path='textChanges.ts' />
/// <reference path='codeFixProvider.ts' />
/// <reference path='codefixes\fixes.ts' />
@ -63,7 +64,7 @@ namespace ts {
return getSourceFileOfNode(this);
}
public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number {
public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number {
return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
}
@ -129,7 +130,7 @@ namespace ts {
return list;
}
private createChildren(sourceFile?: SourceFile) {
private createChildren(sourceFile?: SourceFileLike) {
let children: Node[];
if (this.kind >= SyntaxKind.FirstNode) {
scanner.setText((sourceFile || this.getSourceFile()).text);
@ -182,7 +183,7 @@ namespace ts {
return this._children[index];
}
public getChildren(sourceFile?: SourceFile): Node[] {
public getChildren(sourceFile?: SourceFileLike): Node[] {
if (!this._children) this.createChildren(sourceFile);
return this._children;
}
@ -231,7 +232,7 @@ namespace ts {
return getSourceFileOfNode(this);
}
public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number {
public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number {
return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
}
@ -1682,7 +1683,7 @@ namespace ts {
return [];
}
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const span = { start, length: end - start };
@ -1700,7 +1701,8 @@ namespace ts {
program: program,
newLineCharacter: newLineChar,
host: host,
cancellationToken: cancellationToken
cancellationToken: cancellationToken,
rulesProvider: getRuleProvider(formatOptions)
};
const fixes = codefix.getFixes(context);

666
src/services/textChanges.ts Normal file
View File

@ -0,0 +1,666 @@
/* @internal */
namespace ts.textChanges {
/**
* Currently for simplicity we store recovered positions on the node itself.
* It can be changed to side-table later if we decide that current design is too invasive.
*/
function getPos(n: TextRange) {
return (<any>n)["__pos"];
}
function setPos(n: TextRange, pos: number) {
(<any>n)["__pos"] = pos;
}
function getEnd(n: TextRange) {
return (<any>n)["__end"];
}
function setEnd(n: TextRange, end: number) {
(<any>n)["__end"] = end;
}
export interface ConfigurableStart {
useNonAdjustedStartPosition?: boolean;
}
export interface ConfigurableEnd {
useNonAdjustedEndPosition?: boolean;
}
export enum Position {
FullStart,
Start
}
function skipWhitespacesAndLineBreaks(text: string, start: number) {
return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
}
function hasCommentsBeforeLineBreak(text: string, start: number) {
let i = start;
while (i < text.length) {
const ch = text.charCodeAt(i);
if (isWhiteSpaceSingleLine(ch)) {
i++;
continue;
}
return ch === CharacterCodes.slash;
}
return false;
}
/**
* Usually node.pos points to a position immediately after the previous token.
* If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e:
* const x; // this is x
* ^ - pos for the next variable declaration will point here
* const y; // this is y
* ^ - end for previous variable declaration
* 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
*/
export type ConfigurableStartEnd = ConfigurableStart & ConfigurableEnd;
export interface InsertNodeOptions {
/**
* Text to be inserted before the new node
*/
prefix?: string;
/**
* Text to be inserted after the new node
*/
suffix?: string;
/**
* Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node
*/
indentation?: number;
/**
* Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind
*/
delta?: number;
}
export type ChangeNodeOptions = ConfigurableStartEnd & InsertNodeOptions;
interface Change {
readonly sourceFile: SourceFile;
readonly range: TextRange;
readonly useIndentationFromFile?: boolean;
readonly node?: Node;
readonly options?: ChangeNodeOptions;
}
export function getSeparatorCharacter(separator: Token<SyntaxKind.CommaToken | SyntaxKind.SemicolonToken>) {
return tokenToString(separator.kind);
}
export function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) {
if (options.useNonAdjustedStartPosition) {
return node.getFullStart();
}
const fullStart = node.getFullStart();
const start = node.getStart(sourceFile);
if (fullStart === start) {
return start;
}
const fullStartLine = getLineStartPositionForPosition(fullStart, sourceFile);
const startLine = getLineStartPositionForPosition(start, sourceFile);
if (startLine === fullStartLine) {
// full start and start of the node are on the same line
// a, b;
// ^ ^
// | start
// 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;
}
// get start position of the line following the line that contains fullstart position
let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + 1, sourceFile);
// skip whitespaces/newlines
adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition);
return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile);
}
export function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd) {
if (options.useNonAdjustedEndPosition) {
return node.getEnd();
}
const end = node.getEnd();
const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true);
// check if last character before newPos is linebreak
// if yes - considered all skipped trivia to be trailing trivia of the node
return newEnd !== end && isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))
? newEnd
: end;
}
/**
* Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element
*/
function isSeparator(node: Node, candidate: Node): candidate is Token<SyntaxKind.CommaToken | SyntaxKind.SemicolonToken> {
return candidate && node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression));
}
function spaces(count: number) {
let s = "";
for (let i = 0; i < count; i++) {
s += " ";
}
return s;
}
export class ChangeTracker {
private changes: Change[] = [];
private readonly newLineCharacter: string;
public static fromCodeFixContext(context: CodeFixContext) {
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider);
}
constructor(
private readonly newLine: NewLineKind,
private readonly rulesProvider: formatting.RulesProvider,
private readonly validator?: (text: NonFormattedText) => void) {
this.newLineCharacter = getNewLineCharacter({ newLine });
}
public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } });
return this;
}
public deleteRange(sourceFile: SourceFile, range: TextRange) {
this.changes.push({ sourceFile, range });
return this;
}
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } });
return this;
}
public deleteNodeInList(sourceFile: SourceFile, node: Node) {
const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile);
if (!containingList) {
Debug.fail("node is not a list element");
return this;
}
const index = containingList.indexOf(node);
if (index < 0) {
return this;
}
if (containingList.length === 1) {
this.deleteNode(sourceFile, node);
return this;
}
if (index !== containingList.length - 1) {
const nextToken = getTokenAtPosition(sourceFile, node.end);
if (nextToken && isSeparator(node, nextToken)) {
// find first non-whitespace position in the leading trivia of the node
const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
const nextElement = containingList[index + 1];
/// find first non-whitespace position in the leading trivia of the next node
const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
// shift next node so its first non-whitespace position will be moved to the first non-whitespace position of the deleted node
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
}
else {
const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end);
if (previousToken && isSeparator(node, previousToken)) {
this.deleteNodeRange(sourceFile, previousToken, node);
}
}
return this;
}
public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ sourceFile, range, options, node: newNode });
return this;
}
public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } });
return this;
}
public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } });
return this;
}
public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ sourceFile, options, node: newNode, range: { pos: pos, end: pos } });
return this;
}
public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, options: InsertNodeOptions & ConfigurableStart = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, before, options, Position.Start);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: startPosition } });
return this;
}
public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) {
if ((isStatementButNotDeclaration(after)) ||
after.kind === SyntaxKind.PropertyDeclaration ||
after.kind === SyntaxKind.PropertySignature ||
after.kind === SyntaxKind.MethodSignature) {
// check if previous statement ends with semicolon
// if not - insert semicolon to preserve the code from changing the meaning due to ASI
if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) {
this.changes.push({
sourceFile,
options: {},
range: { pos: after.end, end: after.end },
node: createToken(SyntaxKind.SemicolonToken)
})
}
}
const endPosition = getAdjustedEndPosition(sourceFile, after, options);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: endPosition, end: endPosition } });
return this;
}
/**
* This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range,
* i.e. arguments in arguments lists, parameters in parameter lists etc. Statements or class elements are different in sense that
* for them separators are treated as the part of the node.
*/
public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) {
const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile);
if (!containingList) {
Debug.fail("node is not a list element");
return this;
}
const index = containingList.indexOf(after);
if (index < 0) {
return this;
}
const end = after.getEnd();
if (index !== containingList.length - 1) {
// any element except the last one
// use next sibling as an anchor
const nextToken = getTokenAtPosition(sourceFile, after.end);
if (nextToken && isSeparator(after, nextToken)) {
// for list
// a, b, c
// create change for adding 'e' after 'a' as
// - find start of next element after a (it is b)
// - use this start as start and end position in final change
// - build text of change by formatting the text of node + separator + whitespace trivia of b
// in multiline case it will work as
// a,
// b,
// c,
// result - '*' denotes leading trivia that will be inserted after new text (displayed as '#')
// a,*
//***insertedtext<separator>#
//###b,
// c,
// find line and character of the next element
const lineAndCharOfNextElement = getLineAndCharacterOfPosition(sourceFile, skipWhitespacesAndLineBreaks(sourceFile.text, containingList[index + 1].getFullStart()));
// find line and character of the token that precedes next element (usually it is separator)
const lineAndCharOfNextToken = getLineAndCharacterOfPosition(sourceFile, nextToken.end);
let prefix: string;
let startPos: number;
if (lineAndCharOfNextToken.line === lineAndCharOfNextElement.line) {
// next element is located on the same line with separator:
// a,$$$$b
// ^ ^
// | |-next element
// |-separator
// where $$$ is some leading trivia
// for a newly inserted node we'll maintain the same relative position comparing to separator and replace leading trivia with spaces
// a, x,$$$$b
// ^ ^ ^
// | | |-next element
// | |-new inserted node padded with spaces
// |-separator
startPos = nextToken.end;
prefix = spaces(lineAndCharOfNextElement.character - lineAndCharOfNextToken.character);
}
else {
// next element is located on different line that separator
// let insert position be the beginning of the line that contains next element
startPos = getStartPositionOfLine(lineAndCharOfNextElement.line, sourceFile);
}
this.changes.push({
sourceFile,
range: { pos: startPos, end: containingList[index + 1].getStart(sourceFile) },
node: newNode,
useIndentationFromFile: true,
options: {
prefix,
// write separator and leading trivia of the next element as suffix
suffix: `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, containingList[index + 1].getStart(sourceFile))}`
}
});
}
}
else {
const afterStart = after.getStart(sourceFile);
const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile);
let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken;
let multilineList = false;
// insert element after the last element in the list that has more than one item
// pick the element preceding the after element to:
// - pick the separator
// - determine if list is a multiline
if (containingList.length === 1) {
// if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise
// i.e. var x = 1 // this is x
// | new element will be inserted at this position
separator = SyntaxKind.CommaToken;
}
else {
// element has more than one element, pick separator from the list
const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile);
separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken;
// determine if list is multiline by checking lines of after element and element that precedes it.
const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile);
multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition;
}
if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) {
// in this case we'll always treat containing list as multiline
multilineList = true;
}
if (multilineList) {
// insert separator immediately following the 'after' node to preserve comments in trailing trivia
this.changes.push({
sourceFile,
range: { pos: end, end },
node: createToken(separator),
options: {}
});
// use the same indentation as 'after' item
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.rulesProvider.getFormatOptions());
// insert element before the line break on the line that contains 'after' element
let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false);
if (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) {
insertPos--
}
this.changes.push({
sourceFile,
range: { pos: insertPos, end: insertPos },
node: newNode,
options: { indentation, prefix: this.newLineCharacter }
});
}
else {
this.changes.push({
sourceFile,
range: { pos: end, end },
node: newNode,
options: { prefix: `${tokenToString(separator)} ` }
});
}
}
return this;
}
public getChanges(): FileTextChanges[] {
const changesPerFile = createFileMap<Change[]>();
// group changes per file
for (const c of this.changes) {
let changesInFile = changesPerFile.get(c.sourceFile.path);
if (!changesInFile) {
changesPerFile.set(c.sourceFile.path, changesInFile = []);
};
changesInFile.push(c);
}
// convert changes
const fileChangesList: FileTextChanges[] = [];
changesPerFile.forEachValue(path => {
const changesInFile = changesPerFile.get(path);
const sourceFile = changesInFile[0].sourceFile;
const fileTextChanges: FileTextChanges = { fileName: sourceFile.fileName, textChanges: [] };
for (const c of ChangeTracker.normalize(changesInFile)) {
fileTextChanges.textChanges.push({
span: this.computeSpan(c, sourceFile),
newText: this.computeNewText(c, sourceFile)
});
}
fileChangesList.push(fileTextChanges);
});
return fileChangesList;
}
private computeSpan(change: Change, _sourceFile: SourceFile): TextSpan {
return createTextSpanFromBounds(change.range.pos, change.range.end);
}
private computeNewText(change: Change, sourceFile: SourceFile): string {
if (!change.node) {
// deletion case
return "";
}
const options = change.options || {};
const nonFormattedText = getNonformattedText(change.node, sourceFile, this.newLine);
if (this.validator) {
this.validator(nonFormattedText);
}
const formatOptions = this.rulesProvider.getFormatOptions();
const pos = change.range.pos;
const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos;
const initialIndentation =
change.options.indentation !== undefined
? change.options.indentation
: change.useIndentationFromFile
? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || (change.options.prefix == this.newLineCharacter))
: 0;
const delta =
change.options.delta !== undefined
? change.options.delta
: formatting.SmartIndenter.shouldIndentChildNode(change.node)
? formatOptions.indentSize
: 0;
let text = applyFormatting(nonFormattedText, sourceFile, initialIndentation, delta, this.rulesProvider);
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
// however keep indentation if it is was forced
text = posStartsLine || change.options.indentation !== undefined ? text : text.replace(/^\s+/, "");
return (options.prefix || "") + text + (options.suffix || "");
}
private static normalize(changes: Change[]) {
// order changes by start position
const normalized = stableSort(changes, (a, b) => a.range.pos - b.range.pos);
// verify that end position of the change is less than start position of the next change
for (let i = 0; i < normalized.length - 2; i++) {
Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos);
}
return normalized;
}
}
export interface NonFormattedText {
readonly text: string;
readonly node: Node;
}
export function getNonformattedText(node: Node, sourceFile: SourceFile, newLine: NewLineKind): NonFormattedText {
const options = { newLine, target: sourceFile.languageVersion };
const writer = new Writer(getNewLineCharacter(options));
const printer = createPrinter(options, writer);
printer.writeNode(EmitHint.Unspecified, node, sourceFile, writer);
return { text: writer.getText(), node: assignPositionsToNode(node) };
}
export function applyFormatting(nonFormattedText: NonFormattedText, sourceFile: SourceFile, initialIndentation: number, delta: number, rulesProvider: formatting.RulesProvider) {
const lineMap = computeLineStarts(nonFormattedText.text);
const file: SourceFileLike = {
text: nonFormattedText.text,
lineMap,
getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos)
};
const changes = formatting.formatNode(nonFormattedText.node, file, sourceFile.languageVariant, initialIndentation, delta, rulesProvider);
return applyChanges(nonFormattedText.text, changes);
}
export function applyChanges(text: string, changes: TextChange[]): string {
for (let i = changes.length - 1; i >= 0; i--) {
const change = changes[i];
text = `${text.substring(0, change.span.start)}${change.newText}${text.substring(textSpanEnd(change.span))}`;
}
return text;
}
function isTrivia(s: string) {
return skipTrivia(s, 0) === s.length;
}
const nullTransformationContext: TransformationContext = {
enableEmitNotification: noop,
enableSubstitution: noop,
endLexicalEnvironment: () => undefined,
getCompilerOptions: notImplemented,
getEmitHost: notImplemented,
getEmitResolver: notImplemented,
hoistFunctionDeclaration: noop,
hoistVariableDeclaration: noop,
isEmitNotificationEnabled: notImplemented,
isSubstitutionEnabled: notImplemented,
onEmitNode: noop,
onSubstituteNode: notImplemented,
readEmitHelpers: notImplemented,
requestEmitHelper: noop,
resumeLexicalEnvironment: noop,
startLexicalEnvironment: noop,
suspendLexicalEnvironment: noop
};
function assignPositionsToNode(node: Node): Node {
const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray);
// create proxy node for non synthesized nodes
const newNode = nodeIsSynthesized(visited)
? visited
: (Proxy.prototype = visited, new (<any>Proxy)());
newNode.pos = getPos(node);
newNode.end = getEnd(node);
return newNode;
function Proxy() { }
}
function assignPositionsToNodeArray(nodes: NodeArray<any>, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number) {
const visited = visitNodes(nodes, visitor, test, start, count);
if (!visited) {
return visited;
}
// clone nodearray if necessary
const nodeArray = visited === nodes ? createNodeArray(visited.slice(0)) : visited;
nodeArray.pos = getPos(nodes);
nodeArray.end = getEnd(nodes);
return nodeArray;
}
class Writer implements EmitTextWriter, PrintHandlers {
private lastNonTriviaPosition = 0;
private readonly writer: EmitTextWriter;
public readonly onEmitNode: PrintHandlers["onEmitNode"];
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
constructor(newLine: string) {
this.writer = createTextWriter(newLine);
this.onEmitNode = (hint, node, printCallback) => {
if (node) {
setPos(node, this.lastNonTriviaPosition);
}
printCallback(hint, node);
if (node) {
setEnd(node, this.lastNonTriviaPosition);
}
};
this.onBeforeEmitNodeArray = nodes => {
if (nodes) {
setPos(nodes, this.lastNonTriviaPosition);
}
};
this.onAfterEmitNodeArray = nodes => {
if (nodes) {
setEnd(nodes, this.lastNonTriviaPosition);
}
};
}
private setLastNonTriviaPosition(s: string, force: boolean) {
if (force || !isTrivia(s)) {
this.lastNonTriviaPosition = this.writer.getTextPos();
let i = 0;
while (isWhiteSpace(s.charCodeAt(s.length - i - 1))) {
i++;
}
// trim trailing whitespaces
this.lastNonTriviaPosition -= i;
}
}
write(s: string): void {
this.writer.write(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
}
writeTextOfNode(text: string, node: Node): void {
this.writer.writeTextOfNode(text, node);
}
writeLine(): void {
this.writer.writeLine();
}
increaseIndent(): void {
this.writer.increaseIndent();
}
decreaseIndent(): void {
this.writer.decreaseIndent();
}
getText(): string {
return this.writer.getText();
}
rawWrite(s: string): void {
this.writer.rawWrite(s);
this.setLastNonTriviaPosition(s, /*force*/ false);
}
writeLiteral(s: string): void {
this.writer.writeLiteral(s);
this.setLastNonTriviaPosition(s, /*force*/ true);
}
getTextPos(): number {
return this.writer.getTextPos();
}
getLine(): number {
return this.writer.getLine();
}
getColumn(): number {
return this.writer.getColumn();
}
getIndent(): number {
return this.writer.getIndent();
}
isAtStartOfLine(): boolean {
return this.writer.isAtStartOfLine();
}
reset(): void {
this.writer.reset();
this.lastNonTriviaPosition = 0;
}
}
}

View File

@ -63,6 +63,7 @@
"shims.ts",
"signatureHelp.ts",
"symbolDisplay.ts",
"textChanges.ts",
"formatting/formatting.ts",
"formatting/formattingContext.ts",
"formatting/formattingRequestKind.ts",

View File

@ -4,7 +4,11 @@ namespace ts {
getChildCount(sourceFile?: SourceFile): number;
getChildAt(index: number, sourceFile?: SourceFile): Node;
getChildren(sourceFile?: SourceFile): Node[];
/* @internal */
getChildren(sourceFile?: SourceFileLike): Node[];
getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number;
/* @internal */
getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number;
getFullStart(): number;
getEnd(): number;
getWidth(sourceFile?: SourceFile): number;
@ -59,6 +63,10 @@ namespace ts {
update(newText: string, textChangeRange: TextChangeRange): SourceFile;
}
export interface SourceFileLike {
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
}
/**
* Represents an immutable snapshot of a script at a specified time.Once acquired, the
* snapshot is observably immutable. i.e. the same calls with the same parameters will return
@ -248,7 +256,7 @@ namespace ts {
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[];
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;

View File

@ -394,8 +394,8 @@ namespace ts {
list: Node;
}
export function getLineStartPositionForPosition(position: number, sourceFile: SourceFile): number {
const lineStarts = sourceFile.getLineStarts();
export function getLineStartPositionForPosition(position: number, sourceFile: SourceFileLike): number {
const lineStarts = getLineStarts(sourceFile);
const line = sourceFile.getLineAndCharacterOfPosition(position).line;
return lineStarts[line];
}
@ -604,7 +604,7 @@ namespace ts {
return !!findChildOfKind(n, kind, sourceFile);
}
export function findChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFile): Node | undefined {
export function findChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFileLike): Node | undefined {
return forEach(n.getChildren(sourceFile), c => c.kind === kind && c);
}
@ -1003,10 +1003,6 @@ namespace ts {
return undefined;
}
export function isToken(n: Node): boolean {
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
}
export function isWord(kind: SyntaxKind): boolean {
return kind === SyntaxKind.Identifier || isKeyword(kind);
}
@ -1384,8 +1380,8 @@ namespace ts {
};
}
export function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
export function getOpenBrace(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
// First token is the open curly, this is where we want to put the 'super' call.
return constructor.body.getFirstToken(sourceFile).getEnd();
return constructor.body.getFirstToken(sourceFile);
}
}

View File

@ -0,0 +1,13 @@
===ORIGINAL===
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
===MODIFIED===
var x = 1; // some comment - 1
var z = 3; // comment 4

View File

@ -0,0 +1,12 @@
===ORIGINAL===
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
===MODIFIED===
var x = 1;var z = 3; // comment 4

View File

@ -0,0 +1,14 @@
===ORIGINAL===
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
===MODIFIED===
var x = 1; // some comment - 1
// comment 3
var z = 3; // comment 4

View File

@ -0,0 +1,13 @@
===ORIGINAL===
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
===MODIFIED===
var x = 1; // comment 3
var z = 3; // comment 4

View File

@ -0,0 +1,16 @@
===ORIGINAL===
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
===MODIFIED===
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4

View File

@ -0,0 +1,12 @@
===ORIGINAL===
class A {
x;
y = 1;
}
===MODIFIED===
class A {
y = 1;
}

View File

@ -0,0 +1,12 @@
===ORIGINAL===
class A {
x
y = 1;
}
===MODIFIED===
class A {
y = 1;
}

View File

@ -0,0 +1,4 @@
===ORIGINAL===
var a = 1, b = 2, c = 3;
===MODIFIED===
var b = 2, c = 3;

View File

@ -0,0 +1,10 @@
===ORIGINAL===
function foo(a: number,b: string,c = true) {
return 1;
}
===MODIFIED===
function foo(b: string,c = true) {
return 1;
}

View File

@ -0,0 +1,10 @@
===ORIGINAL===
function foo(a: number,b: string,c = true) {
return 1;
}
===MODIFIED===
function foo(a: number,c = true) {
return 1;
}

View File

@ -0,0 +1,10 @@
===ORIGINAL===
function foo(a: number,b: string,c = true) {
return 1;
}
===MODIFIED===
function foo(a: number,b: string) {
return 1;
}

View File

@ -0,0 +1,15 @@
===ORIGINAL===
function foo(
a: number,
b: string,
c = true) {
return 1;
}
===MODIFIED===
function foo(
b: string,
c = true) {
return 1;
}

View File

@ -0,0 +1,15 @@
===ORIGINAL===
function foo(
a: number,
b: string,
c = true) {
return 1;
}
===MODIFIED===
function foo(
a: number,
c = true) {
return 1;
}

View File

@ -0,0 +1,15 @@
===ORIGINAL===
function foo(
a: number,
b: string,
c = true) {
return 1;
}
===MODIFIED===
function foo(
a: number,
b: string) {
return 1;
}

View File

@ -0,0 +1,4 @@
===ORIGINAL===
var a = 1,b = 2,c = 3;
===MODIFIED===
var b = 2,c = 3;

View File

@ -0,0 +1,4 @@
===ORIGINAL===
var a = 1, b = 2, c = 3;
===MODIFIED===
var a = 1, c = 3;

View File

@ -0,0 +1,4 @@
===ORIGINAL===
var a = 1,b = 2,c = 3;
===MODIFIED===
var a = 1,c = 3;

View File

@ -0,0 +1,4 @@
===ORIGINAL===
var a = 1, b = 2, c = 3;
===MODIFIED===
var a = 1, b = 2;

View File

@ -0,0 +1,4 @@
===ORIGINAL===
var a = 1,b = 2,c = 3;
===MODIFIED===
var a = 1,b = 2;

View File

@ -0,0 +1,13 @@
===ORIGINAL===
namespace M {
var a = 1,
b = 2,
c = 3;
}
===MODIFIED===
namespace M {
var b = 2,
c = 3;
}

View File

@ -0,0 +1,17 @@
===ORIGINAL===
namespace M {
var a = 1, // comment 1
// comment 2
b = 2, // comment 3
// comment 4
c = 3; // comment 5
}
===MODIFIED===
namespace M {
var // comment 2
b = 2, // comment 3
// comment 4
c = 3; // comment 5
}

View File

@ -0,0 +1,13 @@
===ORIGINAL===
namespace M {
var a = 1,
b = 2,
c = 3;
}
===MODIFIED===
namespace M {
var a = 1,
c = 3;
}

View File

@ -0,0 +1,16 @@
===ORIGINAL===
namespace M {
var a = 1, // comment 1
// comment 2
b = 2, // comment 3
// comment 4
c = 3; // comment 5
}
===MODIFIED===
namespace M {
var a = 1, // comment 1
// comment 4
c = 3; // comment 5
}

View File

@ -0,0 +1,13 @@
===ORIGINAL===
namespace M {
var a = 1,
b = 2,
c = 3;
}
===MODIFIED===
namespace M {
var a = 1,
b = 2;
}

View File

@ -0,0 +1,16 @@
===ORIGINAL===
namespace M {
var a = 1, // comment 1
// comment 2
b = 2, // comment 3
// comment 4
c = 3; // comment 5
}
===MODIFIED===
namespace M {
var a = 1, // comment 1
// comment 2
b = 2; // comment 5
}

View File

@ -0,0 +1,10 @@
===ORIGINAL===
function foo(a: number, b: string, c = true) {
return 1;
}
===MODIFIED===
function foo(b: string, c = true) {
return 1;
}

View File

@ -0,0 +1,10 @@
===ORIGINAL===
function foo(a: number, b: string, c = true) {
return 1;
}
===MODIFIED===
function foo(a: number, c = true) {
return 1;
}

View File

@ -0,0 +1,10 @@
===ORIGINAL===
function foo(a: number, b: string, c = true) {
return 1;
}
===MODIFIED===
function foo(a: number, b: string) {
return 1;
}

View File

@ -0,0 +1,16 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1; // comment 2
// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,15 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1;// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,17 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1; // comment 2
// comment 5
// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,16 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1; // comment 5
// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,15 @@
===ORIGINAL===
function foo() {
return 1;
}
function bar() {
return 2;
}
===MODIFIED===
function bar() {
return 2;
}

View File

@ -0,0 +1,49 @@
===ORIGINAL===
namespace M
{
namespace M2
{
function foo() {
// comment 1
const x = 1;
/**
* comment 2 line 1
* comment 2 line 2
*/
function f() {
return 100;
}
const y = 2; // comment 3
return 1;
}
}
}
===MODIFIED===
namespace M
{
function bar(): any
{
/**
* comment 2 line 1
* comment 2 line 2
*/
function f()
{
return 100;
}
const y = 2; // comment 3
return 1;
}
namespace M2
{
function foo() {
// comment 1
const x = 1;
return bar();
}
}
}

View File

@ -0,0 +1,26 @@
===ORIGINAL===
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}
===MODIFIED===
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
public class class1 implements interface1
{
property1: boolean;
}
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}

View File

@ -0,0 +1,26 @@
===ORIGINAL===
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}
===MODIFIED===
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}
public class class1 implements interface1
{
property1: boolean;
}

View File

@ -0,0 +1,16 @@
===ORIGINAL===
class A {
constructor() {
}
}
===MODIFIED===
class A {
constructor() {
super();
}
}

View File

@ -0,0 +1,14 @@
===ORIGINAL===
class A {
constructor() {
}
}
===MODIFIED===
class A {
constructor() {
super();
}
}

View File

@ -0,0 +1,16 @@
===ORIGINAL===
class A {
constructor() {
var x = 1;
}
}
===MODIFIED===
class A {
constructor() {
var x = 1;
super();
}
}

View File

@ -0,0 +1,12 @@
===ORIGINAL===
class A {
x
}
===MODIFIED===
class A {
x;
a: boolean;
}

View File

@ -0,0 +1,12 @@
===ORIGINAL===
class A {
x;
}
===MODIFIED===
class A {
x;
a: boolean;
}

View File

@ -0,0 +1,21 @@
===ORIGINAL===
class A {
x;
}
===MODIFIED===
class A {
x;
0;
1;
2;
3;
4;
5;
6;
7;
8;
9;
10;
}

View File

@ -0,0 +1,22 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1; // comment 2
// comment 3
public class class1 implements interface1
{
property1: boolean;
}
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,20 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1; // comment 2
// comment 3
var yz1 = {
p1: 1
}; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,26 @@
===ORIGINAL===
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}
===MODIFIED===
namespace M {
// comment 1
var x = 1; // comment 2
public class class1 implements interface1
{
property1: boolean;
}
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}

View File

@ -0,0 +1,26 @@
===ORIGINAL===
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}
===MODIFIED===
public class class1 implements interface1
{
property1: boolean;
}
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}

View File

@ -0,0 +1,12 @@
===ORIGINAL===
class A {
x = foo
}
===MODIFIED===
class A {
x = foo;
[1]: any;
}

View File

@ -0,0 +1,14 @@
===ORIGINAL===
class A {
x() {
}
}
===MODIFIED===
class A {
x() {
}
[1]: any;
}

View File

@ -0,0 +1,12 @@
===ORIGINAL===
interface A {
x
}
===MODIFIED===
interface A {
x;
[1]: any;
}

View File

@ -0,0 +1,12 @@
===ORIGINAL===
interface A {
x()
}
===MODIFIED===
interface A {
x();
[1]: any;
}

View File

@ -0,0 +1,6 @@
===ORIGINAL===
const x = 1, y = 2;
===MODIFIED===
const x = 1, z = 1, y = 2;

View File

@ -0,0 +1,10 @@
===ORIGINAL===
import {
x
} from "bar"
===MODIFIED===
import {
x, b as a
} from "bar"

View File

@ -0,0 +1,11 @@
===ORIGINAL===
import {
x // this is x
} from "bar"
===MODIFIED===
import {
x, // this is x
b as a
} from "bar"

View File

@ -0,0 +1,10 @@
===ORIGINAL===
import {
x
} from "bar"
===MODIFIED===
import {
x, a
} from "bar"

View File

@ -0,0 +1,11 @@
===ORIGINAL===
import {
x // this is x
} from "bar"
===MODIFIED===
import {
x, // this is x
a
} from "bar"

View File

@ -0,0 +1,13 @@
===ORIGINAL===
import {
x0,
x
} from "bar"
===MODIFIED===
import {
x0,
x,
b as a
} from "bar"

View File

@ -0,0 +1,13 @@
===ORIGINAL===
import {
x0,
x // this is x
} from "bar"
===MODIFIED===
import {
x0,
x, // this is x
b as a
} from "bar"

View File

@ -0,0 +1,13 @@
===ORIGINAL===
import {
x0,
x
} from "bar"
===MODIFIED===
import {
x0,
x,
a
} from "bar"

View File

@ -0,0 +1,13 @@
===ORIGINAL===
import {
x0,
x // this is x
} from "bar"
===MODIFIED===
import {
x0,
x, // this is x
a
} from "bar"

View File

@ -0,0 +1,10 @@
===ORIGINAL===
import {
x0, x
} from "bar"
===MODIFIED===
import {
x0, x, a
} from "bar"

View File

@ -0,0 +1,6 @@
===ORIGINAL===
const x = 1, y = 2;
===MODIFIED===
const x = 1, y = 2, z = 1;

View File

@ -0,0 +1,6 @@
===ORIGINAL===
const /*x*/ x = 1, /*y*/ y = 2;
===MODIFIED===
const /*x*/ x = 1, z = 1, /*y*/ y = 2;

View File

@ -0,0 +1,6 @@
===ORIGINAL===
const /*x*/ x = 1, /*y*/ y = 2;
===MODIFIED===
const /*x*/ x = 1, /*y*/ y = 2, z = 1;

View File

@ -0,0 +1,6 @@
===ORIGINAL===
const x = 1;
===MODIFIED===
const x = 1, z = 1;

View File

@ -0,0 +1,9 @@
===ORIGINAL===
const x = 1,
y = 2;
===MODIFIED===
const x = 1,
z = 1,
y = 2;

View File

@ -0,0 +1,9 @@
===ORIGINAL===
const x = 1,
y = 2;
===MODIFIED===
const x = 1,
y = 2,
z = 1;

View File

@ -0,0 +1,9 @@
===ORIGINAL===
const /*x*/ x = 1,
/*y*/ y = 2;
===MODIFIED===
const /*x*/ x = 1,
z = 1,
/*y*/ y = 2;

View File

@ -0,0 +1,9 @@
===ORIGINAL===
const /*x*/ x = 1,
/*y*/ y = 2;
===MODIFIED===
const /*x*/ x = 1,
/*y*/ y = 2,
z = 1;

View File

@ -0,0 +1,8 @@
===ORIGINAL===
let x = foo
===MODIFIED===
let x = foo;
(1);

View File

@ -0,0 +1,20 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1; // comment 2
public class class1 implements interface1
{
property1: boolean;
}
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7

View File

@ -0,0 +1,13 @@
===ORIGINAL===
namespace A {
const x = 1, y = "2";
}
===MODIFIED===
namespace A {
const x = 1, z1 = {
p1: 1
};
}

View File

@ -0,0 +1,20 @@
===ORIGINAL===
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
===MODIFIED===
// comment 1
var x = 1;
public class class1 implements interface1
{
property1: boolean;
}
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7

Some files were not shown because too many files have changed in this diff Show More