Ensure locations are propagated everywhere
There were a few places where we didn't propagage source locations correctly. This fixes that. Part of the issue was we didn't have good helper methods to propagate "ranges" (e.g., a sequence of statements or expressions), so this change adds those. This change also establishes a new naming convention; the addLocation* methods adds a newly allocated location based on translating a TypeScript AST node, whereas the copyLocation* methods copy them directly from other MuIL nodes. Thus, there are now four methods: addLocation, addLocationRange, copyLocation, and copyLocationRange.
This commit is contained in:
parent
6c3826659c
commit
b3247a37cd
|
@ -133,6 +133,15 @@ function isComputed(name: ts.Node | undefined): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Translates a TypeScript position into a MuIL position (0 to 1 based lines).
|
||||
function mapPosition(s: ts.SourceFile, p: number): ast.Position {
|
||||
let lc = s.getLineAndCharacterOfPosition(p);
|
||||
return <ast.Position>{
|
||||
line: lc.line + 1, // transform to 1-based line number
|
||||
column: lc.character,
|
||||
};
|
||||
}
|
||||
|
||||
// A transpiler is responsible for transforming TypeScript program artifacts into MuPack/MuIL AST forms.
|
||||
export class Transpiler {
|
||||
private meta: pack.Metadata; // the package's metadata.
|
||||
|
@ -175,36 +184,63 @@ export class Transpiler {
|
|||
/** Helpers **/
|
||||
|
||||
// This annotates a given MuPack/MuIL node with another TypeScript node's source position information.
|
||||
private copyLocation<T extends ast.Node>(src: ts.Node, dst: T): T {
|
||||
let pos = (s: ts.SourceFile, p: number) => {
|
||||
// Translate a TypeScript position into a MuIL position (0 to 1 based lines).
|
||||
let lc = s.getLineAndCharacterOfPosition(p);
|
||||
return {
|
||||
line: lc.line + 1, // transform to 1-based line number
|
||||
column: lc.character,
|
||||
};
|
||||
};
|
||||
|
||||
private addLocation<T extends ast.Node>(src: ts.Node, dst: T): T {
|
||||
// Turn the source file name into one relative to the current root path.
|
||||
let s: ts.SourceFile = src.getSourceFile();
|
||||
let relativePath: string = fspath.relative(this.comp.root, s.fileName);
|
||||
|
||||
dst.loc = {
|
||||
file: relativePath,
|
||||
start: pos(s, src.getStart()),
|
||||
end: pos(s, src.getEnd()),
|
||||
start: mapPosition(s, src.getStart()),
|
||||
end: mapPosition(s, src.getEnd()),
|
||||
};
|
||||
|
||||
// Despite mutating in place, we return the node to facilitate a more fluent style.
|
||||
return dst;
|
||||
}
|
||||
|
||||
// This annotates a given MuPack/MuIL node with a range of TypeScript node source positions.
|
||||
private addLocationRange<T extends ast.Node>(start: ts.Node, end: ts.Node, dst: T): T {
|
||||
contract.assert(start.getSourceFile() === end.getSourceFile());
|
||||
// Turn the source file name into one relative to the current root path.
|
||||
let s: ts.SourceFile = start.getSourceFile();
|
||||
let relativePath: string = fspath.relative(this.comp.root, s.fileName);
|
||||
dst.loc = {
|
||||
file: relativePath,
|
||||
start: mapPosition(s, start.getStart()),
|
||||
end: mapPosition(s, end.getEnd()),
|
||||
};
|
||||
|
||||
// Despite mutating in place, we return the node to facilitate a more fluent style.
|
||||
return dst;
|
||||
}
|
||||
|
||||
// This just carries forward an existing location from another node.
|
||||
private copyLocation<T extends ast.Node>(src: ast.Node, dst: T): T {
|
||||
dst.loc = src.loc;
|
||||
return dst;
|
||||
}
|
||||
|
||||
// This just carries forward an existing location range from another node.
|
||||
private copyLocationRange<T extends ast.Node>(start: ast.Node, end: ast.Node, dst: T): T {
|
||||
let sloc: ast.Location | undefined = start.loc || end.loc;
|
||||
let eloc: ast.Location | undefined = end.loc || start.loc;
|
||||
if (sloc !== undefined && eloc !== undefined) {
|
||||
contract.assert(sloc.file === eloc.file);
|
||||
dst.loc = {
|
||||
file: sloc.file,
|
||||
start: sloc.start,
|
||||
end: eloc.end,
|
||||
};
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/** Transformations **/
|
||||
|
||||
/** Symbols **/
|
||||
|
||||
private transformIdentifier(node: ts.Identifier): ast.Identifier {
|
||||
return this.copyLocation(node, ident(node.text));
|
||||
return this.addLocation(node, ident(node.text));
|
||||
}
|
||||
|
||||
/** Modules **/
|
||||
|
@ -268,7 +304,7 @@ export class Transpiler {
|
|||
moduleName = moduleName.substring(0, moduleExtIndex);
|
||||
}
|
||||
|
||||
return this.copyLocation(node, <ast.Module>{
|
||||
return this.addLocation(node, <ast.Module>{
|
||||
kind: ast.moduleKind,
|
||||
name: ident(moduleName),
|
||||
members: members,
|
||||
|
@ -440,7 +476,7 @@ export class Transpiler {
|
|||
if (statement.kind === ast.blockKind) {
|
||||
return <ast.Block>statement;
|
||||
}
|
||||
return this.copyLocation(node, <ast.Block>{
|
||||
return this.addLocation(node, <ast.Block>{
|
||||
kind: ast.blockKind,
|
||||
statements: [ statement ],
|
||||
});
|
||||
|
@ -518,7 +554,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node);
|
||||
return this.copyLocation(node, <ast.Class>{
|
||||
return this.addLocation(node, <ast.Class>{
|
||||
kind: ast.classKind,
|
||||
name: name,
|
||||
access: access,
|
||||
|
@ -626,7 +662,7 @@ export class Transpiler {
|
|||
private transformModuleFunctionDeclaration(
|
||||
node: ts.FunctionDeclaration, access: symbols.Accessibility): ast.ModuleMethod {
|
||||
let decl: FunctionLikeDeclaration = this.transformFunctionLikeDeclaration(node);
|
||||
return this.copyLocation(node, <ast.ModuleMethod>{
|
||||
return this.addLocation(node, <ast.ModuleMethod>{
|
||||
kind: ast.moduleMethodKind,
|
||||
name: decl.name,
|
||||
access: access,
|
||||
|
@ -675,7 +711,7 @@ export class Transpiler {
|
|||
|
||||
private makeVariableInitializer(decl: VariableDeclaration<ast.Variable>): ast.Statement {
|
||||
contract.requires(!!decl.initializer, "decl", "Expected variable declaration to have an initializer");
|
||||
return this.copyLocation(decl.node, {
|
||||
return this.addLocation(decl.node, {
|
||||
kind: ast.binaryOperatorExpressionKind,
|
||||
left: <ast.LoadLocationExpression>{
|
||||
kind: ast.loadLocationExpressionKind,
|
||||
|
@ -727,14 +763,21 @@ export class Transpiler {
|
|||
statements.push(this.makeVariableInitializer(vdecl));
|
||||
}
|
||||
}
|
||||
if (statements.length === 1) {
|
||||
return statements[0];
|
||||
|
||||
contract.assert(statements.length > 0);
|
||||
|
||||
if (statements.length > 1) {
|
||||
return this.copyLocationRange(
|
||||
statements[0],
|
||||
statements[statements.length-1],
|
||||
<ast.MultiStatement>{
|
||||
kind: ast.multiStatementKind,
|
||||
statements: statements,
|
||||
},
|
||||
);
|
||||
}
|
||||
else {
|
||||
return <ast.MultiStatement>{
|
||||
kind: ast.multiStatementKind,
|
||||
statements: statements,
|
||||
};
|
||||
return statements[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -823,7 +866,7 @@ export class Transpiler {
|
|||
|
||||
let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node);
|
||||
let decl: FunctionLikeDeclaration = this.transformFunctionLikeDeclaration(node);
|
||||
return this.copyLocation(node, <ast.ClassMethod>{
|
||||
return this.addLocation(node, <ast.ClassMethod>{
|
||||
kind: ast.classMethodKind,
|
||||
name: decl.name,
|
||||
access: this.getClassAccessibility(node),
|
||||
|
@ -861,7 +904,7 @@ export class Transpiler {
|
|||
/** Control flow statements **/
|
||||
|
||||
private transformBreakStatement(node: ts.BreakStatement): ast.BreakStatement {
|
||||
return this.copyLocation(node, <ast.BreakStatement>{
|
||||
return this.addLocation(node, <ast.BreakStatement>{
|
||||
kind: ast.breakStatementKind,
|
||||
label: object.maybeUndefined(node.label, (id: ts.Identifier) => this.transformIdentifier(id)),
|
||||
});
|
||||
|
@ -876,7 +919,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformContinueStatement(node: ts.ContinueStatement): ast.ContinueStatement {
|
||||
return this.copyLocation(node, <ast.ContinueStatement>{
|
||||
return this.addLocation(node, <ast.ContinueStatement>{
|
||||
kind: ast.continueStatementKind,
|
||||
label: object.maybeUndefined(node.label, (id: ts.Identifier) => this.transformIdentifier(id)),
|
||||
});
|
||||
|
@ -885,7 +928,7 @@ export class Transpiler {
|
|||
// This transforms a higher-level TypeScript `do`/`while` statement by expanding into an ordinary `while` statement.
|
||||
private transformDoStatement(node: ts.DoStatement): ast.WhileStatement {
|
||||
// Now create a new block that simply concatenates the existing one with a test/`break`.
|
||||
let body: ast.Block = this.copyLocation(node.statement, {
|
||||
let body: ast.Block = this.addLocation(node.statement, {
|
||||
kind: ast.blockKind,
|
||||
statements: [
|
||||
this.transformStatement(node.statement),
|
||||
|
@ -903,7 +946,7 @@ export class Transpiler {
|
|||
],
|
||||
});
|
||||
|
||||
return this.copyLocation(node, <ast.WhileStatement>{
|
||||
return this.addLocation(node, <ast.WhileStatement>{
|
||||
kind: ast.whileStatementKind,
|
||||
test: <ast.BoolLiteral>{
|
||||
kind: ast.boolLiteralKind,
|
||||
|
@ -930,7 +973,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformIfStatement(node: ts.IfStatement): ast.IfStatement {
|
||||
return this.copyLocation(node, <ast.IfStatement>{
|
||||
return this.addLocation(node, <ast.IfStatement>{
|
||||
kind: ast.ifStatementKind,
|
||||
condition: this.transformExpression(node.expression),
|
||||
consequent: this.transformStatement(node.thenStatement),
|
||||
|
@ -940,7 +983,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformReturnStatement(node: ts.ReturnStatement): ast.ReturnStatement {
|
||||
return this.copyLocation(node, <ast.ReturnStatement>{
|
||||
return this.addLocation(node, <ast.ReturnStatement>{
|
||||
kind: ast.returnStatementKind,
|
||||
expression: object.maybeUndefined(
|
||||
node.expression, (expr: ts.Expression) => this.transformExpression(expr)),
|
||||
|
@ -952,7 +995,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformThrowStatement(node: ts.ThrowStatement): ast.ThrowStatement {
|
||||
return this.copyLocation(node, <ast.ThrowStatement>{
|
||||
return this.addLocation(node, <ast.ThrowStatement>{
|
||||
kind: ast.throwStatementKind,
|
||||
expression: this.transformExpression(node.expression),
|
||||
});
|
||||
|
@ -963,7 +1006,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformWhileStatement(node: ts.WhileStatement): ast.WhileStatement {
|
||||
return this.copyLocation(node, <ast.WhileStatement>{
|
||||
return this.addLocation(node, <ast.WhileStatement>{
|
||||
kind: ast.whileStatementKind,
|
||||
test: this.transformExpression(node.expression),
|
||||
body: this.transformStatementAsBlock(node.statement),
|
||||
|
@ -974,7 +1017,7 @@ export class Transpiler {
|
|||
|
||||
private transformBlock(node: ts.Block): ast.Block {
|
||||
// TODO(joe): map directives.
|
||||
return this.copyLocation(node, <ast.Block>{
|
||||
return this.addLocation(node, <ast.Block>{
|
||||
kind: ast.blockKind,
|
||||
statements: node.statements.map((stmt: ts.Statement) => this.transformStatement(stmt)),
|
||||
});
|
||||
|
@ -983,26 +1026,26 @@ export class Transpiler {
|
|||
private transformDebuggerStatement(node: ts.DebuggerStatement): ast.Statement {
|
||||
// The debugger statement in ECMAScript can be used to trip a breakpoint. We don't have the equivalent in Mu at
|
||||
// the moment, so we simply produce an empty statement in its place.
|
||||
return this.copyLocation(node, <ast.Statement>{
|
||||
return this.addLocation(node, <ast.Statement>{
|
||||
kind: ast.emptyStatementKind,
|
||||
});
|
||||
}
|
||||
|
||||
private transformEmptyStatement(node: ts.EmptyStatement): ast.EmptyStatement {
|
||||
return this.copyLocation(node, <ast.EmptyStatement>{
|
||||
return this.addLocation(node, <ast.EmptyStatement>{
|
||||
kind: ast.emptyStatementKind,
|
||||
});
|
||||
}
|
||||
|
||||
private transformExpressionStatement(node: ts.ExpressionStatement): ast.ExpressionStatement {
|
||||
return this.copyLocation(node, <ast.ExpressionStatement>{
|
||||
return this.addLocation(node, <ast.ExpressionStatement>{
|
||||
kind: ast.expressionStatementKind,
|
||||
expression: this.transformExpression(node.expression),
|
||||
});
|
||||
}
|
||||
|
||||
private transformLabeledStatement(node: ts.LabeledStatement): ast.LabeledStatement {
|
||||
return this.copyLocation(node, <ast.LabeledStatement>{
|
||||
return this.addLocation(node, <ast.LabeledStatement>{
|
||||
kind: ast.labeledStatementKind,
|
||||
label: this.transformIdentifier(node.label),
|
||||
statement: this.transformStatement(node.statement),
|
||||
|
@ -1120,7 +1163,7 @@ export class Transpiler {
|
|||
// TODO: finish binary operator mapping; for any left that are unsupported, introduce a real error message.
|
||||
return contract.fail(`Unsupported binary operator: ${ts.SyntaxKind[node.operatorToken.kind]}`);
|
||||
}
|
||||
return this.copyLocation(node, <ast.BinaryOperatorExpression>{
|
||||
return this.addLocation(node, <ast.BinaryOperatorExpression>{
|
||||
kind: ast.binaryOperatorExpressionKind,
|
||||
operator: operator,
|
||||
left: this.transformExpression(node.left),
|
||||
|
@ -1130,6 +1173,8 @@ export class Transpiler {
|
|||
|
||||
private transformBinarySequenceExpression(node: ts.BinaryExpression): ast.SequenceExpression {
|
||||
contract.assert(node.operatorToken.kind === ts.SyntaxKind.CommaToken);
|
||||
|
||||
// Pile up the expressions in the right order.
|
||||
let curr: ts.Expression = node;
|
||||
let binary: ts.BinaryExpression = node;
|
||||
let expressions: ast.Expression[] = [];
|
||||
|
@ -1139,14 +1184,20 @@ export class Transpiler {
|
|||
curr = binary.left;
|
||||
}
|
||||
expressions.unshift(this.transformExpression(curr));
|
||||
return <ast.SequenceExpression>{
|
||||
kind: ast.sequenceExpressionKind,
|
||||
expressions: expressions,
|
||||
};
|
||||
|
||||
contract.assert(expressions.length > 0);
|
||||
return this.copyLocationRange(
|
||||
expressions[0],
|
||||
expressions[expressions.length-1],
|
||||
<ast.SequenceExpression>{
|
||||
kind: ast.sequenceExpressionKind,
|
||||
expressions: expressions,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private transformCallExpression(node: ts.CallExpression): ast.InvokeFunctionExpression {
|
||||
return this.copyLocation(node, <ast.InvokeFunctionExpression>{
|
||||
return this.addLocation(node, <ast.InvokeFunctionExpression>{
|
||||
kind: ast.invokeFunctionExpressionKind,
|
||||
function: this.transformExpression(node.expression),
|
||||
arguments: node.arguments.map((expr: ts.Expression) => this.transformExpression(expr)),
|
||||
|
@ -1158,7 +1209,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformConditionalExpression(node: ts.ConditionalExpression): ast.ConditionalExpression {
|
||||
return this.copyLocation(node, <ast.ConditionalExpression>{
|
||||
return this.addLocation(node, <ast.ConditionalExpression>{
|
||||
kind: ast.conditionalExpressionKind,
|
||||
condition: this.transformExpression(node.condition),
|
||||
consequent: this.transformExpression(node.whenTrue),
|
||||
|
@ -1175,13 +1226,13 @@ export class Transpiler {
|
|||
if (node.argumentExpression) {
|
||||
switch (node.argumentExpression.kind) {
|
||||
case ts.SyntaxKind.Identifier:
|
||||
return this.copyLocation(node, <ast.LoadLocationExpression>{
|
||||
return this.addLocation(node, <ast.LoadLocationExpression>{
|
||||
kind: ast.loadLocationExpressionKind,
|
||||
object: object,
|
||||
name: this.transformIdentifier(<ts.Identifier>node.argumentExpression),
|
||||
});
|
||||
default:
|
||||
return this.copyLocation(node, <ast.LoadDynamicExpression>{
|
||||
return this.addLocation(node, <ast.LoadDynamicExpression>{
|
||||
kind: ast.loadDynamicExpressionKind,
|
||||
object: object,
|
||||
name: this.transformExpression(<ts.Expression>node.argumentExpression),
|
||||
|
@ -1218,7 +1269,7 @@ export class Transpiler {
|
|||
private transformPostfixUnaryExpression(node: ts.PostfixUnaryExpression): ast.UnaryOperatorExpression {
|
||||
let operator: ast.UnaryOperator | undefined = postfixUnaryOperators.get(node.operator);
|
||||
contract.assert(!!(operator = operator!));
|
||||
return this.copyLocation(node, <ast.UnaryOperatorExpression>{
|
||||
return this.addLocation(node, <ast.UnaryOperatorExpression>{
|
||||
kind: ast.unaryOperatorExpressionKind,
|
||||
postfix: true,
|
||||
operator: operator,
|
||||
|
@ -1229,7 +1280,7 @@ export class Transpiler {
|
|||
private transformPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ast.UnaryOperatorExpression {
|
||||
let operator: ast.UnaryOperator | undefined = prefixUnaryOperators.get(node.operator);
|
||||
contract.assert(!!(operator = operator!));
|
||||
return this.copyLocation(node, <ast.UnaryOperatorExpression>{
|
||||
return this.addLocation(node, <ast.UnaryOperatorExpression>{
|
||||
kind: ast.unaryOperatorExpressionKind,
|
||||
postfix: false,
|
||||
operator: operator,
|
||||
|
@ -1238,7 +1289,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformPropertyAccessExpression(node: ts.PropertyAccessExpression): ast.LoadLocationExpression {
|
||||
return this.copyLocation(node, <ast.LoadLocationExpression>{
|
||||
return this.addLocation(node, <ast.LoadLocationExpression>{
|
||||
kind: ast.loadLocationExpressionKind,
|
||||
object: this.transformExpression(node.expression),
|
||||
name: this.transformIdentifier(node.name),
|
||||
|
@ -1255,11 +1306,11 @@ export class Transpiler {
|
|||
return contract.fail("New T(..) expression must have an identifier T");
|
||||
}
|
||||
|
||||
return <ast.NewExpression>{
|
||||
return this.addLocation(node, <ast.NewExpression>{
|
||||
kind: ast.newExpressionKind,
|
||||
type: ty,
|
||||
arguments: node.arguments.map((expr: ts.Expression) => this.transformExpression(expr)),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private transformOmittedExpression(node: ts.OmittedExpression): ast.Expression {
|
||||
|
@ -1275,7 +1326,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformSuperExpression(node: ts.SuperExpression): ast.LoadLocationExpression {
|
||||
return this.copyLocation(node, <ast.LoadLocationExpression>{
|
||||
return this.addLocation(node, <ast.LoadLocationExpression>{
|
||||
kind: ast.loadLocationExpressionKind,
|
||||
name: ident(symbols.specialVariableSuper),
|
||||
});
|
||||
|
@ -1290,7 +1341,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformThisExpression(node: ts.ThisExpression): ast.LoadLocationExpression {
|
||||
return this.copyLocation(node, <ast.LoadLocationExpression>{
|
||||
return this.addLocation(node, <ast.LoadLocationExpression>{
|
||||
kind: ast.loadLocationExpressionKind,
|
||||
name: ident(symbols.specialVariableThis),
|
||||
});
|
||||
|
@ -1312,7 +1363,7 @@ export class Transpiler {
|
|||
|
||||
private transformBooleanLiteral(node: ts.BooleanLiteral): ast.BoolLiteral {
|
||||
contract.assert(node.kind === ts.SyntaxKind.FalseKeyword || node.kind === ts.SyntaxKind.TrueKeyword);
|
||||
return this.copyLocation(node, <ast.BoolLiteral>{
|
||||
return this.addLocation(node, <ast.BoolLiteral>{
|
||||
kind: ast.boolLiteralKind,
|
||||
raw: node.getText(),
|
||||
value: (node.kind === ts.SyntaxKind.TrueKeyword),
|
||||
|
@ -1324,14 +1375,14 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformNullLiteral(node: ts.NullLiteral): ast.NullLiteral {
|
||||
return this.copyLocation(node, <ast.NullLiteral>{
|
||||
return this.addLocation(node, <ast.NullLiteral>{
|
||||
kind: ast.nullLiteralKind,
|
||||
raw: node.getText(),
|
||||
});
|
||||
}
|
||||
|
||||
private transformNumericLiteral(node: ts.NumericLiteral): ast.NumberLiteral {
|
||||
return this.copyLocation(node, <ast.NumberLiteral>{
|
||||
return this.addLocation(node, <ast.NumberLiteral>{
|
||||
kind: ast.numberLiteralKind,
|
||||
raw: node.text,
|
||||
value: Number(node.text),
|
||||
|
@ -1346,7 +1397,7 @@ export class Transpiler {
|
|||
// TODO: we need to dynamically populate the resulting object with ECMAScript-style string functions. It's not
|
||||
// yet clear how to do this in a way that facilitates inter-language interoperability. This is especially
|
||||
// challenging because most use of such functions will be entirely dynamic.
|
||||
return this.copyLocation(node, <ast.StringLiteral>{
|
||||
return this.addLocation(node, <ast.StringLiteral>{
|
||||
kind: ast.stringLiteralKind,
|
||||
raw: node.text,
|
||||
value: node.text,
|
||||
|
@ -1382,7 +1433,7 @@ export class Transpiler {
|
|||
}
|
||||
|
||||
private transformIdentifierExpression(node: ts.Identifier): ast.Identifier {
|
||||
return this.copyLocation(node, ident(node.text));
|
||||
return this.addLocation(node, ident(node.text));
|
||||
}
|
||||
|
||||
private transformObjectBindingPattern(node: ts.ObjectBindingPattern): ast.Expression {
|
||||
|
|
|
@ -561,7 +561,18 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
"start": {
|
||||
"line": 14,
|
||||
"column": 15
|
||||
},
|
||||
"end": {
|
||||
"line": 14,
|
||||
"column": 60
|
||||
}
|
||||
}
|
||||
},
|
||||
"loc": {
|
||||
"file": "index.ts",
|
||||
|
|
Loading…
Reference in a new issue