Skip to content

Commit

Permalink
Make the indent rules supports ECMAScript 2020
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Mar 11, 2020
1 parent 800182c commit 9cfa4bf
Show file tree
Hide file tree
Showing 32 changed files with 451 additions and 23 deletions.
2 changes: 1 addition & 1 deletion docs/rules/html-indent.md
Expand Up @@ -15,7 +15,7 @@ description: enforce consistent indentation in `<template>`
This rule enforces a consistent indentation style in `<template>`. The default style is 2 spaces.

- This rule checks all tags, also all expressions in directives and mustaches.
- In the expressions, this rule supports ECMAScript 2017 syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.
- In the expressions, this rule supports ECMAScript 2020 syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.

<eslint-code-block fix :rules="{'vue/html-indent': ['error']}">

Expand Down
108 changes: 90 additions & 18 deletions lib/utils/indent-common.js
Expand Up @@ -14,7 +14,7 @@ const assert = require('assert')
// Helpers
// ------------------------------------------------------------------------------

const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionExpression', 'AssignmentExpression', 'AssignmentPattern', 'AwaitExpression', 'BinaryExpression', 'BlockStatement', 'BreakStatement', 'CallExpression', 'CatchClause', 'ClassBody', 'ClassDeclaration', 'ClassExpression', 'ConditionalExpression', 'ContinueStatement', 'DebuggerStatement', 'DoWhileStatement', 'EmptyStatement', 'ExperimentalRestProperty', 'ExperimentalSpreadProperty', 'ExportAllDeclaration', 'ExportDefaultDeclaration', 'ExportNamedDeclaration', 'ExportSpecifier', 'ExpressionStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'FunctionDeclaration', 'FunctionExpression', 'Identifier', 'IfStatement', 'ImportDeclaration', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier', 'ImportSpecifier', 'LabeledStatement', 'Literal', 'LogicalExpression', 'MemberExpression', 'MetaProperty', 'MethodDefinition', 'NewExpression', 'ObjectExpression', 'ObjectPattern', 'Program', 'Property', 'RestElement', 'ReturnStatement', 'SequenceExpression', 'SpreadElement', 'Super', 'SwitchCase', 'SwitchStatement', 'TaggedTemplateExpression', 'TemplateElement', 'TemplateLiteral', 'ThisExpression', 'ThrowStatement', 'TryStatement', 'UnaryExpression', 'UpdateExpression', 'VariableDeclaration', 'VariableDeclarator', 'WhileStatement', 'WithStatement', 'YieldExpression', 'VAttribute', 'VDirectiveKey', 'VDocumentFragment', 'VElement', 'VEndTag', 'VExpressionContainer', 'VFilter', 'VFilterSequenceExpression', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VSlotScopeExpression', 'VStartTag', 'VText'])
const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionExpression', 'AssignmentExpression', 'AssignmentPattern', 'AwaitExpression', 'BinaryExpression', 'BlockStatement', 'BreakStatement', 'CallExpression', 'CatchClause', 'ClassBody', 'ClassDeclaration', 'ClassExpression', 'ConditionalExpression', 'ContinueStatement', 'DebuggerStatement', 'DoWhileStatement', 'EmptyStatement', 'ExperimentalRestProperty', 'ExperimentalSpreadProperty', 'ExportAllDeclaration', 'ExportDefaultDeclaration', 'ExportDefaultSpecifier', 'ExportNamedDeclaration', 'ExportNamespaceSpecifier', 'ExportSpecifier', 'ExpressionStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'FunctionDeclaration', 'FunctionExpression', 'Identifier', 'IfStatement', 'ImportDeclaration', 'ImportDefaultSpecifier', 'ImportExpression', 'ImportNamespaceSpecifier', 'ImportSpecifier', 'LabeledStatement', 'Literal', 'LogicalExpression', 'MemberExpression', 'MetaProperty', 'MethodDefinition', 'NewExpression', 'ObjectExpression', 'ObjectPattern', 'OptionalCallExpression', 'OptionalMemberExpression', 'Program', 'Property', 'RestElement', 'ReturnStatement', 'SequenceExpression', 'SpreadElement', 'Super', 'SwitchCase', 'SwitchStatement', 'TaggedTemplateExpression', 'TemplateElement', 'TemplateLiteral', 'ThisExpression', 'ThrowStatement', 'TryStatement', 'UnaryExpression', 'UpdateExpression', 'VariableDeclaration', 'VariableDeclarator', 'WhileStatement', 'WithStatement', 'YieldExpression', 'VAttribute', 'VDirectiveKey', 'VDocumentFragment', 'VElement', 'VEndTag', 'VExpressionContainer', 'VFilter', 'VFilterSequenceExpression', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VSlotScopeExpression', 'VStartTag', 'VText'])
const LT_CHAR = /[\r\n\u2028\u2029]/
const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
const BLOCK_COMMENT_PREFIX = /^\s*\*/
Expand Down Expand Up @@ -552,7 +552,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
}
return true
}
if (t === 'CallExpression' || t === 'NewExpression') {
if (t === 'CallExpression' || t === 'NewExpression' || t === 'OptionalCallExpression') {
const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen)
return parent.arguments.some(param =>
getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0]
Expand Down Expand Up @@ -1065,7 +1065,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
}
},

CallExpression (node) {
'CallExpression, OptionalCallExpression' (node) {
const firstToken = tokenStore.getFirstToken(node)
const rightToken = tokenStore.getLastToken(node)
const leftToken = tokenStore.getTokenAfter(node.callee, isLeftParen)
Expand All @@ -1074,6 +1074,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
processNodeList(node.arguments, leftToken, rightToken, 1)
},

ImportExpression (node) {
const firstToken = tokenStore.getFirstToken(node)
const rightToken = tokenStore.getLastToken(node)
const leftToken = tokenStore.getTokenAfter(firstToken, isLeftParen)

setOffset(leftToken, 1, firstToken)
processNodeList([node.source], leftToken, rightToken, 1)
},

CatchClause (node) {
const firstToken = tokenStore.getFirstToken(node)
const bodyToken = tokenStore.getFirstToken(node.body)
Expand Down Expand Up @@ -1145,7 +1154,18 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
if (isSemicolon(last(tokens))) {
tokens.pop()
}
setOffset(tokens, 1, firstToken)
if (!node.exported) {
setOffset(tokens, 1, firstToken)
} else {
// export * as foo from "mod"
const asToken = tokenStore.getTokenBefore(node.exported)
const exportedTokens = tokenStore.getTokensBetween(asToken, node.exported, {
includeComments: true
})
const oneIndentTokens = tokens.filter(t => !exportedTokens.includes(t))
setOffset(oneIndentTokens, 1, firstToken)
setOffset(exportedTokens, 1, tokenStore.getTokenBefore(asToken))
}
},

ExportDefaultDeclaration (node) {
Expand All @@ -1162,17 +1182,65 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
const declarationToken = tokenStore.getFirstToken(node, 1)
setOffset(declarationToken, 1, exportToken)
} else {
// export {foo, bar}; or export {foo, bar} from "mod";
const leftParenToken = tokenStore.getFirstToken(node, 1)
const rightParenToken = tokenStore.getLastToken(node, isRightBrace)
setOffset(leftParenToken, 0, exportToken)
processNodeList(node.specifiers, leftParenToken, rightParenToken, 1)

const maybeFromToken = tokenStore.getTokenAfter(rightParenToken)
if (maybeFromToken != null && sourceCode.getText(maybeFromToken) === 'from') {
const fromToken = maybeFromToken
const nameToken = tokenStore.getTokenAfter(fromToken)
setOffset([fromToken, nameToken], 1, exportToken)
const firstSpecifier = node.specifiers[0]
const secondSpecifier = node.specifiers[1]
if (!firstSpecifier || firstSpecifier.type === 'ExportSpecifier') {
// export {foo, bar}; or export {foo, bar} from "mod";
const leftParenToken = tokenStore.getFirstToken(node, 1)
const rightParenToken = tokenStore.getLastToken(node, isRightBrace)
setOffset(leftParenToken, 0, exportToken)
processNodeList(node.specifiers, leftParenToken, rightParenToken, 1)

const maybeFromToken = tokenStore.getTokenAfter(rightParenToken)
if (maybeFromToken != null && sourceCode.getText(maybeFromToken) === 'from') {
const fromToken = maybeFromToken
const nameToken = tokenStore.getTokenAfter(fromToken)
setOffset([fromToken, nameToken], 1, exportToken)
}
} else if (firstSpecifier.type === 'ExportDefaultSpecifier') {
// for babel-eslint
if (secondSpecifier && secondSpecifier.type === 'ExportNamespaceSpecifier') {
// There is a pattern:
// export Foo, * as foo from "foo"
const idToken = tokenStore.getFirstToken(firstSpecifier)// Foo
const commaToken = tokenStore.getTokenAfter(firstSpecifier) // comma
const astaToken = tokenStore.getFirstToken(secondSpecifier)// *
const fromToken = tokenStore.getTokenAfter(secondSpecifier) // from
const modToken = tokenStore.getTokenAfter(fromToken)// "foo"
setOffset([idToken, commaToken, astaToken, fromToken, modToken], 1, exportToken)
} else {
// There are 3 patterns:
// export Foo from "foo"
// export Foo, {} from "foo"
// export Foo, {a} from "foo"
const idToken = tokenStore.getFirstToken(firstSpecifier)
const nextToken = tokenStore.getTokenAfter(firstSpecifier)
if (isComma(nextToken)) {
const leftBrace = tokenStore.getTokenAfter(nextToken)
const rightBrace = tokenStore.getLastToken(node, isRightBrace)
setOffset([idToken, nextToken], 1, exportToken)
setOffset(leftBrace, 0, idToken)
processNodeList(node.specifiers.slice(1), leftBrace, rightBrace, 1)
const fromToken = tokenStore.getTokenAfter(rightBrace) // from
const modToken = tokenStore.getTokenAfter(fromToken)// "foo"
setOffset([fromToken, modToken], 1, exportToken)
} else {
const modToken = tokenStore.getTokenAfter(nextToken)// "foo"
setOffset([
idToken,
nextToken, // from
modToken
], 1, exportToken)
}
}
} else if (firstSpecifier.type === 'ExportNamespaceSpecifier') {
// for babel-eslint
// There is a pattern:
// export * as foo from "foo"
const astaToken = tokenStore.getFirstToken(firstSpecifier)
const fromToken = tokenStore.getTokenAfter(firstSpecifier)
const modToken = tokenStore.getTokenAfter(fromToken)
setOffset([astaToken, fromToken, modToken], 1, exportToken)
}
}
},
Expand All @@ -1185,12 +1253,16 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti

'ForInStatement, ForOfStatement' (node) {
const forToken = tokenStore.getFirstToken(node)
const leftParenToken = tokenStore.getTokenAfter(forToken)
const awaitToken = (node.await && tokenStore.getTokenAfter(forToken)) || null
const leftParenToken = tokenStore.getTokenAfter(awaitToken || forToken)
const leftToken = tokenStore.getTokenAfter(leftParenToken)
const inToken = tokenStore.getTokenAfter(leftToken, isNotRightParen)
const rightToken = tokenStore.getTokenAfter(inToken)
const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)

if (awaitToken != null) {
setOffset(awaitToken, 0, forToken)
}
setOffset(leftParenToken, 1, forToken)
setOffset(leftToken, 1, leftParenToken)
setOffset(inToken, 1, leftToken)
Expand Down Expand Up @@ -1353,7 +1425,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
}
},

ImportNamespaceSpecifier (node) {
'ImportNamespaceSpecifier, ExportNamespaceSpecifier' (node) {
const tokens = tokenStore.getTokens(node)
const firstToken = tokens.shift()
setOffset(tokens, 1, firstToken)
Expand All @@ -1367,7 +1439,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
setOffset([colonToken, bodyToken], 1, labelToken)
},

'MemberExpression, MetaProperty' (node) {
'MemberExpression, MetaProperty, OptionalMemberExpression' (node) {
const objectToken = tokenStore.getFirstToken(node)
if (node.computed) {
const leftBracketToken = tokenStore.getTokenBefore(node.property, isLeftBracket)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -53,7 +53,7 @@
},
"devDependencies": {
"@types/node": "^4.2.16",
"@typescript-eslint/parser": "^2.6.1",
"@typescript-eslint/parser": "^2.13.0",
"acorn": "^7.1.0",
"babel-eslint": "^10.0.2",
"chai": "^4.1.0",
Expand All @@ -66,7 +66,7 @@
"lodash": "^4.17.4",
"mocha": "^5.2.0",
"nyc": "^12.0.2",
"typescript": "^3.5.2",
"typescript": "^3.7.4",
"vue-eslint-editor": "^0.1.4",
"vuepress": "^0.14.5"
}
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/html-indent/nullish-coalescing-operator-01.vue
@@ -0,0 +1,8 @@
<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
<!-- TODO If ESLint supports the new syntax by default, remove the parser specification and test. -->
<template>
<div v-bind:a="a
??
b
"/>
</template>
11 changes: 11 additions & 0 deletions tests/fixtures/html-indent/optional-chaining-01.vue
@@ -0,0 +1,11 @@
<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
<!-- TODO If ESLint supports the new syntax by default, remove the parser specification and test. -->

<template>
<div v-bind:a="
obj
?.aaa
?.bbb
?.ccc
"/>
</template>
10 changes: 10 additions & 0 deletions tests/fixtures/html-indent/optional-chaining-02.vue
@@ -0,0 +1,10 @@
<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
<!-- TODO If ESLint supports the new syntax by default, remove the parser specification and test. -->
<template>
<div v-bind:a="
obj?.
aaa?.
bbb?.
ccc
"/>
</template>
10 changes: 10 additions & 0 deletions tests/fixtures/html-indent/optional-chaining-03.vue
@@ -0,0 +1,10 @@
<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
<!-- TODO If ESLint supports the new syntax by default, remove the parser specification and test. -->
<template>
<div v-bind:a="
obj?.
[aaa]?.
[bbb]
?.[ccc]
"/>
</template>
16 changes: 16 additions & 0 deletions tests/fixtures/html-indent/optional-chaining-04.vue
@@ -0,0 +1,16 @@
<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
<!-- TODO If ESLint supports the new syntax by default, remove the parser specification and test. -->
<template>
<div v-bind:a="
obj?.
(
aaa
)?.
(
bbb
)
?.(
ccc
)
"/>
</template>
7 changes: 7 additions & 0 deletions tests/fixtures/script-indent/bigint-01.vue
@@ -0,0 +1,7 @@
<!--{}-->
<script>
var a =
10n
+
5n
</script>
17 changes: 17 additions & 0 deletions tests/fixtures/script-indent/export-all-declaration-02.vue
@@ -0,0 +1,17 @@
<!--{"parserOptions": {"parser":"babel-eslint"}}-->
<!-- TODO If ESLint supports the new syntax by default, remove the parser specification and test. -->
<script>
export
*
as
foo
from
"mod"
;
export
*
as
bar
from
"mod"
</script>
12 changes: 12 additions & 0 deletions tests/fixtures/script-indent/export-default-specifier-babel-01.vue
@@ -0,0 +1,12 @@
<!--{"parserOptions": {"parser":"babel-eslint"}}-->
<script>
export
Foo
from
"mod"
;
export
Bar
from
"mod"
</script>
30 changes: 30 additions & 0 deletions tests/fixtures/script-indent/export-default-specifier-babel-02.vue
@@ -0,0 +1,30 @@
<!--{"parserOptions": {"parser":"babel-eslint"}}-->
<script>
export
Foo
,
{
a
as
A
,
b
as
B
}
from
"mod"
;
export
Bar,
{
c
as
C,
d
as
D
}
from
"mod"
</script>
19 changes: 19 additions & 0 deletions tests/fixtures/script-indent/export-default-specifier-babel-03.vue
@@ -0,0 +1,19 @@
<!--{"parserOptions": {"parser":"babel-eslint"}}-->
<script>
export
Foo
,
*
as
fooAll
from
"mod"
;
export
Bar,
*
as
barAll
from
"mod"
</script>
@@ -0,0 +1,16 @@
<!--{"parserOptions": {"parser":"babel-eslint"}}-->
<script>
export
*
as
foo
from
"mod"
;
export
*
as
bar
from
"mod"
</script>
15 changes: 15 additions & 0 deletions tests/fixtures/script-indent/for-await-of-01.vue
@@ -0,0 +1,15 @@
<!--{}-->
<script>
async function fn() {
for
await
(
a
of
b
)
{
;
}
}
</script>
7 changes: 7 additions & 0 deletions tests/fixtures/script-indent/import-expression-01.vue
@@ -0,0 +1,7 @@
<!--{}-->
<script>
const fs = import
(
'fs'
)
</script>

0 comments on commit 9cfa4bf

Please sign in to comment.