diff --git a/src/script/index.ts b/src/script/index.ts index 59c5937a..90ecf0fa 100644 --- a/src/script/index.ts +++ b/src/script/index.ts @@ -5,6 +5,7 @@ */ import first from "lodash/first" import last from "lodash/last" +import sortedIndexBy from "lodash/sortedIndexBy" import { traverseNodes, ESLintArrayPattern, @@ -18,7 +19,6 @@ import { ESLintFunctionExpression, ESLintPattern, ESLintProgram, - ESLintSpreadElement, ESLintVariableDeclaration, ESLintUnaryExpression, Node, @@ -150,6 +150,30 @@ function removeByName(references: Reference[], name: string): void { } } +/** + * Get the comma token before a given node. + * @param tokens The token list. + * @param node The node to get the comma before this node. + * @returns The comma token. + */ +function getCommaTokenBeforeNode(tokens: Token[], node: Node): Token | null { + let tokenIndex = sortedIndexBy( + tokens, + { range: node.range }, + t => t.range[0], + ) + + while (tokenIndex >= 0) { + const token = tokens[tokenIndex] + if (token.type === "Punctuator" && token.value === ",") { + return token + } + tokenIndex -= 1 + } + + return null +} + /** * Throw syntax error for empty. * @param locationCalculator The location calculator to get line/column. @@ -172,22 +196,19 @@ function throwEmptyError( } /** - * Throw syntax error for empty. + * Throw syntax error for unexpected token. * @param locationCalculator The location calculator to get line/column. + * @param name The token name. + * @param token The token object to get that location. */ -function throwUnexpectedSpreadElementError( - locationCalculator: LocationCalculator, - node: ESLintSpreadElement, -): never { - const loc = locationCalculator.getLocation(node.start || 0) +function throwUnexpectedTokenError(name: string, token: Node | Token): never { const err = new ParseError( - "Unexpected spread element.", + `Unexpected token '${name}'.`, undefined, - 0, - loc.line, - loc.column, + token.range[0], + token.loc.start.line, + token.loc.start.column, ) - locationCalculator.fixErrorLocation(err) throw err } @@ -364,13 +385,17 @@ export function parseExpression( return throwEmptyError(locationCalculator, "an expression") } if (expression && expression.type === "SpreadElement") { - return throwUnexpectedSpreadElementError( - locationCalculator.getSubCalculatorAfter(-2), - expression, + return throwUnexpectedTokenError("...", expression) + } + if (callExpression.arguments[1]) { + const node = callExpression.arguments[1] + return throwUnexpectedTokenError( + ",", + getCommaTokenBeforeNode(tokens, node) || node, ) } - // Remvoe parens. + // Remove parens. tokens.shift() tokens.shift() tokens.pop() diff --git a/test/fixtures/ast/v-bind-sequence-expression/ast.json b/test/fixtures/ast/v-bind-sequence-expression/ast.json new file mode 100644 index 00000000..bb87a785 --- /dev/null +++ b/test/fixtures/ast/v-bind-sequence-expression/ast.json @@ -0,0 +1,845 @@ +{ + "type": "Program", + "start": 0, + "end": 0, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 0 + } + }, + "range": [ + 0, + 0 + ], + "body": [], + "sourceType": "script", + "comments": [], + "tokens": [], + "templateBody": { + "type": "VElement", + "range": [ + 0, + 76 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "name": "template", + "rawName": "template", + "namespace": "http://www.w3.org/1999/xhtml", + "startTag": { + "type": "VStartTag", + "range": [ + 0, + 10 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "selfClosing": false, + "attributes": [] + }, + "children": [ + { + "type": "VText", + "range": [ + 10, + 15 + ], + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 2, + "column": 4 + } + }, + "value": "\n " + }, + { + "type": "VElement", + "range": [ + 15, + 64 + ], + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 4, + "column": 11 + } + }, + "name": "div", + "rawName": "div", + "namespace": "http://www.w3.org/1999/xhtml", + "startTag": { + "type": "VStartTag", + "range": [ + 15, + 58 + ], + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 4, + "column": 5 + } + }, + "selfClosing": false, + "attributes": [ + { + "type": "VAttribute", + "range": [ + 20, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "directive": true, + "key": { + "type": "VDirectiveKey", + "range": [ + 20, + 22 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 11 + } + }, + "name": "bind", + "argument": "a", + "modifiers": [], + "shorthand": true, + "raw": { + "name": "bind", + "argument": "a", + "modifiers": [] + } + }, + "value": { + "type": "VExpressionContainer", + "range": [ + 23, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "expression": null, + "references": [] + } + }, + { + "type": "VAttribute", + "range": [ + 40, + 52 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "directive": true, + "key": { + "type": "VDirectiveKey", + "range": [ + 40, + 42 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 11 + } + }, + "name": "bind", + "argument": "b", + "modifiers": [], + "shorthand": true, + "raw": { + "name": "bind", + "argument": "b", + "modifiers": [] + } + }, + "value": { + "type": "VExpressionContainer", + "range": [ + 43, + 52 + ], + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "expression": { + "type": "SequenceExpression", + "start": 45, + "end": 50, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 19 + } + }, + "range": [ + 45, + 50 + ], + "expressions": [ + { + "type": "Identifier", + "start": 45, + "end": 47, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 16 + } + }, + "range": [ + 45, + 47 + ], + "name": "b1" + }, + { + "type": "Identifier", + "start": 48, + "end": 50, + "loc": { + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 19 + } + }, + "range": [ + 48, + 50 + ], + "name": "b2" + } + ] + }, + "references": [ + { + "id": { + "type": "Identifier", + "start": 45, + "end": 47, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 16 + } + }, + "range": [ + 45, + 47 + ], + "name": "b1" + }, + "mode": "r" + }, + { + "id": { + "type": "Identifier", + "start": 48, + "end": 50, + "loc": { + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 19 + } + }, + "range": [ + 48, + 50 + ], + "name": "b2" + }, + "mode": "r" + } + ] + } + } + ] + }, + "children": [], + "endTag": { + "type": "VEndTag", + "range": [ + 58, + 64 + ], + "loc": { + "start": { + "line": 4, + "column": 5 + }, + "end": { + "line": 4, + "column": 11 + } + } + }, + "variables": [] + }, + { + "type": "VText", + "range": [ + 64, + 65 + ], + "loc": { + "start": { + "line": 4, + "column": 11 + }, + "end": { + "line": 5, + "column": 0 + } + }, + "value": "\n" + } + ], + "endTag": { + "type": "VEndTag", + "range": [ + 65, + 76 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 11 + } + } + }, + "variables": [], + "tokens": [ + { + "type": "HTMLTagOpen", + "range": [ + 0, + 9 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "value": "template" + }, + { + "type": "HTMLTagClose", + "range": [ + 9, + 10 + ], + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "value": "" + }, + { + "type": "HTMLWhitespace", + "range": [ + 10, + 15 + ], + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 2, + "column": 4 + } + }, + "value": "\n " + }, + { + "type": "HTMLTagOpen", + "range": [ + 15, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 2, + "column": 8 + } + }, + "value": "div" + }, + { + "type": "HTMLIdentifier", + "range": [ + 20, + 22 + ], + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 11 + } + }, + "value": ":a" + }, + { + "type": "HTMLAssociation", + "range": [ + 22, + 23 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "value": "" + }, + { + "type": "HTMLLiteral", + "range": [ + 23, + 30 + ], + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "value": "a1,a2" + }, + { + "type": "HTMLIdentifier", + "range": [ + 40, + 42 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 11 + } + }, + "value": ":b" + }, + { + "type": "HTMLAssociation", + "range": [ + 42, + 43 + ], + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "value": "" + }, + { + "type": "Punctuator", + "range": [ + 43, + 44 + ], + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "value": "\"" + }, + { + "type": "Punctuator", + "value": "(", + "start": 44, + "end": 45, + "loc": { + "start": { + "line": 3, + "column": 13 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "range": [ + 44, + 45 + ] + }, + { + "type": "Identifier", + "value": "b1", + "start": 45, + "end": 47, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 16 + } + }, + "range": [ + 45, + 47 + ] + }, + { + "type": "Punctuator", + "value": ",", + "start": 47, + "end": 48, + "loc": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 3, + "column": 17 + } + }, + "range": [ + 47, + 48 + ] + }, + { + "type": "Identifier", + "value": "b2", + "start": 48, + "end": 50, + "loc": { + "start": { + "line": 3, + "column": 17 + }, + "end": { + "line": 3, + "column": 19 + } + }, + "range": [ + 48, + 50 + ] + }, + { + "type": "Punctuator", + "value": ")", + "start": 50, + "end": 51, + "loc": { + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "range": [ + 50, + 51 + ] + }, + { + "type": "Punctuator", + "range": [ + 51, + 52 + ], + "loc": { + "start": { + "line": 3, + "column": 20 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "value": "\"" + }, + { + "type": "HTMLTagClose", + "range": [ + 57, + 58 + ], + "loc": { + "start": { + "line": 4, + "column": 4 + }, + "end": { + "line": 4, + "column": 5 + } + }, + "value": "" + }, + { + "type": "HTMLEndTagOpen", + "range": [ + 58, + 63 + ], + "loc": { + "start": { + "line": 4, + "column": 5 + }, + "end": { + "line": 4, + "column": 10 + } + }, + "value": "div" + }, + { + "type": "HTMLTagClose", + "range": [ + 63, + 64 + ], + "loc": { + "start": { + "line": 4, + "column": 10 + }, + "end": { + "line": 4, + "column": 11 + } + }, + "value": "" + }, + { + "type": "HTMLWhitespace", + "range": [ + 64, + 65 + ], + "loc": { + "start": { + "line": 4, + "column": 11 + }, + "end": { + "line": 5, + "column": 0 + } + }, + "value": "\n" + }, + { + "type": "HTMLEndTagOpen", + "range": [ + 65, + 75 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 10 + } + }, + "value": "template" + }, + { + "type": "HTMLTagClose", + "range": [ + 75, + 76 + ], + "loc": { + "start": { + "line": 5, + "column": 10 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "value": "" + }, + { + "type": "HTMLWhitespace", + "range": [ + 76, + 77 + ], + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 6, + "column": 0 + } + }, + "value": "\n" + } + ], + "comments": [], + "errors": [ + { + "message": "Unexpected token ','.", + "index": 26, + "lineNumber": 2, + "column": 15 + } + ] + } +} \ No newline at end of file diff --git a/test/fixtures/ast/v-bind-sequence-expression/source.vue b/test/fixtures/ast/v-bind-sequence-expression/source.vue new file mode 100644 index 00000000..6086327b --- /dev/null +++ b/test/fixtures/ast/v-bind-sequence-expression/source.vue @@ -0,0 +1,5 @@ + diff --git a/test/fixtures/ast/v-bind-sequence-expression/token-ranges.json b/test/fixtures/ast/v-bind-sequence-expression/token-ranges.json new file mode 100644 index 00000000..a702de10 --- /dev/null +++ b/test/fixtures/ast/v-bind-sequence-expression/token-ranges.json @@ -0,0 +1,25 @@ +[ + "", + "\n ", + "", + "", + "\n", + "", + "\n" +] \ No newline at end of file diff --git a/test/fixtures/ast/v-bind-sequence-expression/tree.json b/test/fixtures/ast/v-bind-sequence-expression/tree.json new file mode 100644 index 00000000..ebc22614 --- /dev/null +++ b/test/fixtures/ast/v-bind-sequence-expression/tree.json @@ -0,0 +1,94 @@ +[ + { + "type": "VElement", + "text": "", + "children": [ + { + "type": "VStartTag", + "text": "", + "children": [] + } + ] + } +] \ No newline at end of file