diff --git a/docs/rules/html-indent.md b/docs/rules/html-indent.md
index d82ff86eb..c3a45f273 100644
--- a/docs/rules/html-indent.md
+++ b/docs/rules/html-indent.md
@@ -15,7 +15,7 @@ description: enforce consistent indentation in ``
This rule enforces a consistent indentation style in ``. 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.
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 718f04278..9e6bfdeff 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -12,6 +12,7 @@
// Helpers
// ------------------------------------------------------------------------------
+/** @type {Set} */
const KNOWN_NODES = new Set([
'ArrayExpression',
'ArrayPattern',
@@ -24,6 +25,7 @@ const KNOWN_NODES = new Set([
'BreakStatement',
'CallExpression',
'CatchClause',
+ 'ChainExpression',
'ClassBody',
'ClassDeclaration',
'ClassExpression',
@@ -32,8 +34,6 @@ const KNOWN_NODES = new Set([
'DebuggerStatement',
'DoWhileStatement',
'EmptyStatement',
- 'ExperimentalRestProperty',
- 'ExperimentalSpreadProperty',
'ExportAllDeclaration',
'ExportDefaultDeclaration',
'ExportNamedDeclaration',
@@ -48,6 +48,7 @@ const KNOWN_NODES = new Set([
'IfStatement',
'ImportDeclaration',
'ImportDefaultSpecifier',
+ 'ImportExpression',
'ImportNamespaceSpecifier',
'ImportSpecifier',
'LabeledStatement',
@@ -97,6 +98,10 @@ const KNOWN_NODES = new Set([
'VStartTag',
'VText'
])
+const NON_STANDARD_KNOWN_NODES = new Set([
+ 'ExperimentalRestProperty',
+ 'ExperimentalSpreadProperty'
+])
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*\*/
@@ -300,6 +305,14 @@ function isSemicolon(token) {
function isComma(token) {
return token != null && token.type === 'Punctuator' && token.value === ','
}
+/**
+ * Check whether the given token is a wildcard.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a wildcard.
+ */
+function isWildcard(token) {
+ return token != null && token.type === 'Punctuator' && token.value === '*'
+}
/**
* Check whether the given token is a whitespace.
@@ -321,7 +334,9 @@ function isComment(token) {
(token.type === 'Block' ||
token.type === 'Line' ||
token.type === 'Shebang' ||
- token.type.endsWith('Comment'))
+ (typeof token.type ===
+ 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ &&
+ token.type.endsWith('Comment')))
)
}
@@ -336,7 +351,11 @@ function isNotComment(token) {
token.type !== 'Block' &&
token.type !== 'Line' &&
token.type !== 'Shebang' &&
- !token.type.endsWith('Comment')
+ !(
+ typeof token.type ===
+ 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ &&
+ token.type.endsWith('Comment')
+ )
)
}
@@ -1330,6 +1349,15 @@ module.exports.defineVisitor = function create(
setOffset(leftToken, 1, firstToken)
processNodeList(node.arguments, leftToken, rightToken, 1)
},
+ /** @param {ImportExpression} node */
+ 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)
+ },
/** @param {CatchClause} node */
CatchClause(node) {
const firstToken = tokenStore.getFirstToken(node)
@@ -1417,7 +1445,20 @@ module.exports.defineVisitor = function create(
if (isSemicolon(last(tokens))) {
tokens.pop()
}
- setOffset(tokens, 1, firstToken)
+ if (!node.exported) {
+ setOffset(tokens, 1, firstToken)
+ } else {
+ // export * as foo from "mod"
+ const starToken = /** @type {Token} */ (tokens.find(isWildcard))
+ const asToken = tokenStore.getTokenAfter(starToken)
+ const exportedToken = tokenStore.getTokenAfter(asToken)
+ const afterTokens = tokens.slice(tokens.indexOf(exportedToken) + 1)
+
+ setOffset(starToken, 1, firstToken)
+ setOffset(asToken, 1, starToken)
+ setOffset(exportedToken, 1, starToken)
+ setOffset(afterTokens, 1, firstToken)
+ }
},
/** @param {ExportDefaultDeclaration} node */
ExportDefaultDeclaration(node) {
@@ -1435,23 +1476,28 @@ module.exports.defineVisitor = function create(
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 = /** @type {Token} */ (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]
+ if (!firstSpecifier || firstSpecifier.type === 'ExportSpecifier') {
+ // export {foo, bar}; or export {foo, bar} from "mod";
+ const leftParenToken = tokenStore.getFirstToken(node, 1)
+ const rightParenToken = /** @type {Token} */ (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 {
+ // maybe babel-eslint
}
}
},
@@ -1464,7 +1510,12 @@ module.exports.defineVisitor = function create(
/** @param {ForInStatement | ForOfStatement} node */
'ForInStatement, ForOfStatement'(node) {
const forToken = tokenStore.getFirstToken(node)
- const leftParenToken = tokenStore.getTokenAfter(forToken)
+ const awaitToken =
+ (node.type === 'ForOfStatement' &&
+ node.await &&
+ tokenStore.getTokenAfter(forToken)) ||
+ null
+ const leftParenToken = tokenStore.getTokenAfter(awaitToken || forToken)
const leftToken = tokenStore.getTokenAfter(leftParenToken)
const inToken = /** @type {Token} */ (tokenStore.getTokenAfter(
leftToken,
@@ -1476,6 +1527,9 @@ module.exports.defineVisitor = function create(
isNotLeftParen
)
+ if (awaitToken != null) {
+ setOffset(awaitToken, 0, forToken)
+ }
setOffset(leftParenToken, 1, forToken)
setOffset(leftToken, 1, leftParenToken)
setOffset(inToken, 1, leftToken)
@@ -1958,7 +2012,10 @@ module.exports.defineVisitor = function create(
/** @param {ASTNode} node */
// Ignore tokens of unknown nodes.
'*:exit'(node) {
- if (!KNOWN_NODES.has(node.type)) {
+ if (
+ !KNOWN_NODES.has(node.type) &&
+ !NON_STANDARD_KNOWN_NODES.has(node.type)
+ ) {
ignore(node)
}
},
diff --git a/package.json b/package.json
index 4291451fd..512de1597 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"@types/eslint": "^6.8.1",
"@types/natural-compare": "^1.4.0",
"@types/node": "^13.13.5",
- "@typescript-eslint/parser": "^2.31.0",
+ "@typescript-eslint/parser": "^3.0.2",
"@vuepress/plugin-pwa": "^1.4.1",
"babel-eslint": "^10.1.0",
"chai": "^4.2.0",
diff --git a/tests/fixtures/html-indent/nullish-coalescing-operator-01.vue b/tests/fixtures/html-indent/nullish-coalescing-operator-01.vue
new file mode 100644
index 000000000..927d62769
--- /dev/null
+++ b/tests/fixtures/html-indent/nullish-coalescing-operator-01.vue
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/tests/fixtures/html-indent/optional-chaining-01.vue b/tests/fixtures/html-indent/optional-chaining-01.vue
new file mode 100644
index 000000000..6a6078e95
--- /dev/null
+++ b/tests/fixtures/html-indent/optional-chaining-01.vue
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/tests/fixtures/html-indent/optional-chaining-02.vue b/tests/fixtures/html-indent/optional-chaining-02.vue
new file mode 100644
index 000000000..3d53c6018
--- /dev/null
+++ b/tests/fixtures/html-indent/optional-chaining-02.vue
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/tests/fixtures/html-indent/optional-chaining-03.vue b/tests/fixtures/html-indent/optional-chaining-03.vue
new file mode 100644
index 000000000..ff81bb467
--- /dev/null
+++ b/tests/fixtures/html-indent/optional-chaining-03.vue
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/tests/fixtures/html-indent/optional-chaining-04.vue b/tests/fixtures/html-indent/optional-chaining-04.vue
new file mode 100644
index 000000000..f064b73cb
--- /dev/null
+++ b/tests/fixtures/html-indent/optional-chaining-04.vue
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/tests/fixtures/script-indent/bigint-01.vue b/tests/fixtures/script-indent/bigint-01.vue
new file mode 100644
index 000000000..4e8e6c20b
--- /dev/null
+++ b/tests/fixtures/script-indent/bigint-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/export-all-declaration-02.vue b/tests/fixtures/script-indent/export-all-declaration-02.vue
new file mode 100644
index 000000000..b85213a6b
--- /dev/null
+++ b/tests/fixtures/script-indent/export-all-declaration-02.vue
@@ -0,0 +1,16 @@
+
+
diff --git a/tests/fixtures/script-indent/for-await-of-01.vue b/tests/fixtures/script-indent/for-await-of-01.vue
new file mode 100644
index 000000000..b5c07e2c1
--- /dev/null
+++ b/tests/fixtures/script-indent/for-await-of-01.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/tests/fixtures/script-indent/import-expression-01.vue b/tests/fixtures/script-indent/import-expression-01.vue
new file mode 100644
index 000000000..3f7c7cb61
--- /dev/null
+++ b/tests/fixtures/script-indent/import-expression-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/import-expression-02.vue b/tests/fixtures/script-indent/import-expression-02.vue
new file mode 100644
index 000000000..8144c3ffa
--- /dev/null
+++ b/tests/fixtures/script-indent/import-expression-02.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/import-expression-03.vue b/tests/fixtures/script-indent/import-expression-03.vue
new file mode 100644
index 000000000..e82a6df42
--- /dev/null
+++ b/tests/fixtures/script-indent/import-expression-03.vue
@@ -0,0 +1,11 @@
+
+
diff --git a/tests/fixtures/script-indent/nullish-coalescing-operator-01.vue b/tests/fixtures/script-indent/nullish-coalescing-operator-01.vue
new file mode 100644
index 000000000..22427c7f6
--- /dev/null
+++ b/tests/fixtures/script-indent/nullish-coalescing-operator-01.vue
@@ -0,0 +1,6 @@
+
+
diff --git a/tests/fixtures/script-indent/object-expression-04.vue b/tests/fixtures/script-indent/object-expression-04.vue
new file mode 100644
index 000000000..25e8c2077
--- /dev/null
+++ b/tests/fixtures/script-indent/object-expression-04.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-01.vue b/tests/fixtures/script-indent/optional-chaining-01.vue
new file mode 100644
index 000000000..f0ea36ee4
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-01.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-02.vue b/tests/fixtures/script-indent/optional-chaining-02.vue
new file mode 100644
index 000000000..11c77d518
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-02.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-03.vue b/tests/fixtures/script-indent/optional-chaining-03.vue
new file mode 100644
index 000000000..01626acf0
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-03.vue
@@ -0,0 +1,7 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-04.vue b/tests/fixtures/script-indent/optional-chaining-04.vue
new file mode 100644
index 000000000..65a42a74d
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-04.vue
@@ -0,0 +1,13 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-05.vue b/tests/fixtures/script-indent/optional-chaining-05.vue
new file mode 100644
index 000000000..0e926b497
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-05.vue
@@ -0,0 +1,11 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-06.vue b/tests/fixtures/script-indent/optional-chaining-06.vue
new file mode 100644
index 000000000..47898015b
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-06.vue
@@ -0,0 +1,25 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-07.vue b/tests/fixtures/script-indent/optional-chaining-07.vue
new file mode 100644
index 000000000..1ba9fb0fe
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-07.vue
@@ -0,0 +1,25 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-08.vue b/tests/fixtures/script-indent/optional-chaining-08.vue
new file mode 100644
index 000000000..61d071887
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-08.vue
@@ -0,0 +1,12 @@
+
+
diff --git a/tests/fixtures/script-indent/optional-chaining-09.vue b/tests/fixtures/script-indent/optional-chaining-09.vue
new file mode 100644
index 000000000..c17562c0c
--- /dev/null
+++ b/tests/fixtures/script-indent/optional-chaining-09.vue
@@ -0,0 +1,32 @@
+
+
diff --git a/tests/fixtures/script-indent/rest-properties-01.vue b/tests/fixtures/script-indent/rest-properties-01.vue
new file mode 100644
index 000000000..87ab2ab9b
--- /dev/null
+++ b/tests/fixtures/script-indent/rest-properties-01.vue
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/script-indent/try-statement-04.vue b/tests/fixtures/script-indent/try-statement-04.vue
new file mode 100644
index 000000000..4b3d9e866
--- /dev/null
+++ b/tests/fixtures/script-indent/try-statement-04.vue
@@ -0,0 +1,14 @@
+
+
diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js
index 875b5df78..d70b03957 100644
--- a/tests/lib/rules/html-indent.js
+++ b/tests/lib/rules/html-indent.js
@@ -40,6 +40,14 @@ function loadPatterns(additionalValid, additionalInvalid) {
const code0 = fs.readFileSync(path.join(FIXTURE_ROOT, filename), 'utf8')
const code = code0.replace(/^/, ``)
const baseObj = JSON.parse(/^/.exec(code0)[1])
+ if ('parser' in baseObj) {
+ baseObj.parser = require.resolve(baseObj.parser)
+ }
+ if ('parserOptions' in baseObj && 'parser' in baseObj.parserOptions) {
+ baseObj.parserOptions.parser = require.resolve(
+ baseObj.parserOptions.parser
+ )
+ }
return Object.assign(baseObj, { code, filename })
})
const invalid = valid
@@ -104,7 +112,7 @@ function unIndent(strings) {
const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
- ecmaVersion: 2017,
+ ecmaVersion: 2020,
ecmaFeatures: {
globalReturn: true
}
diff --git a/tests/lib/rules/script-indent.js b/tests/lib/rules/script-indent.js
index d74d41dd3..42497d2dd 100644
--- a/tests/lib/rules/script-indent.js
+++ b/tests/lib/rules/script-indent.js
@@ -41,6 +41,14 @@ function loadPatterns(additionalValid, additionalInvalid) {
const code0 = fs.readFileSync(path.join(FIXTURE_ROOT, filename), 'utf8')
const code = code0.replace(commentPattern, `$1${filename}$3`)
const baseObj = JSON.parse(commentPattern.exec(code0)[2])
+ if ('parser' in baseObj) {
+ baseObj.parser = require.resolve(baseObj.parser)
+ }
+ if ('parserOptions' in baseObj && 'parser' in baseObj.parserOptions) {
+ baseObj.parserOptions.parser = require.resolve(
+ baseObj.parserOptions.parser
+ )
+ }
return Object.assign(baseObj, { code, filename })
})
const invalid = valid
@@ -105,7 +113,7 @@ function unIndent(strings) {
const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
- ecmaVersion: 2017,
+ ecmaVersion: 2020,
sourceType: 'module'
}
})
diff --git a/typings/eslint-plugin-vue/global.d.ts b/typings/eslint-plugin-vue/global.d.ts
index 268cbbb18..aa23a1a21 100644
--- a/typings/eslint-plugin-vue/global.d.ts
+++ b/typings/eslint-plugin-vue/global.d.ts
@@ -121,6 +121,8 @@ declare global {
type ClassExpression = VAST.ClassExpression
type MetaProperty = VAST.MetaProperty
type AwaitExpression = VAST.AwaitExpression
+ type ChainExpression = VAST.ChainExpression
+ type ChainElement = VAST.ChainElement
type Property = VAST.Property
type AssignmentProperty = VAST.AssignmentProperty
type Super = VAST.Super
@@ -143,6 +145,7 @@ declare global {
type ImportDefaultSpecifier = VAST.ImportDefaultSpecifier
type ImportNamespaceSpecifier = VAST.ImportNamespaceSpecifier
type ExportSpecifier = VAST.ExportSpecifier
+ type ImportExpression = VAST.ImportExpression
// ---- TS Nodes ----
diff --git a/typings/eslint-plugin-vue/util-types/ast/ast.ts b/typings/eslint-plugin-vue/util-types/ast/ast.ts
index dc55396eb..bcdeb93c6 100644
--- a/typings/eslint-plugin-vue/util-types/ast/ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/ast.ts
@@ -259,6 +259,10 @@ export type ESNodeListenerMap = {
'ImportNamespaceSpecifier:exit': ES.ImportNamespaceSpecifier
ExportSpecifier: ES.ExportSpecifier
'ExportSpecifier:exit': ES.ExportSpecifier
+ ImportExpression: ES.ImportExpression
+ 'ImportExpression:exit': ES.ImportExpression
+ ChainExpression: ES.ChainExpression
+ 'ChainExpression:exit': ES.ChainExpression
TSAsExpression: TS.TSAsExpression
'TSAsExpression:exit': TS.TSAsExpression
diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
index efc81f169..701de116d 100644
--- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts
@@ -113,6 +113,7 @@ export interface ForOfStatement extends HasParentNode {
left: VariableDeclaration | Pattern
right: Expression
body: Statement
+ await: boolean
}
export interface LabeledStatement extends HasParentNode {
type: 'LabeledStatement'
@@ -143,7 +144,7 @@ export interface TryStatement extends HasParentNode {
}
export interface CatchClause extends HasParentNode {
type: 'CatchClause'
- param: Pattern
+ param: Pattern | null
body: BlockStatement
}
export interface WithStatement extends HasParentNode {
@@ -243,6 +244,11 @@ export interface ExportDefaultDeclaration extends HasParentNode {
export interface ExportAllDeclaration extends HasParentNode {
type: 'ExportAllDeclaration'
source: Literal
+ exported: Identifier | null
+}
+export interface ImportExpression extends HasParentNode {
+ type: 'ImportExpression'
+ source: Expression
}
export type Expression =
| ThisExpression
@@ -268,6 +274,8 @@ export type Expression =
| MetaProperty
| Identifier
| AwaitExpression
+ | ImportExpression
+ | ChainExpression
| JSX.JSXElement
| JSX.JSXFragment
| TS.TSAsExpression
@@ -404,7 +412,7 @@ export interface UpdateExpression extends HasParentNode {
}
export interface LogicalExpression extends HasParentNode {
type: 'LogicalExpression'
- operator: '||' | '&&'
+ operator: '||' | '&&' | '??'
left: Expression
right: Expression
}
@@ -418,6 +426,7 @@ export interface CallExpression extends HasParentNode {
type: 'CallExpression'
callee: Expression | Super
arguments: (Expression | SpreadElement)[]
+ optional: boolean
}
export interface Super extends HasParentNode {
type: 'Super'
@@ -432,7 +441,13 @@ export interface MemberExpression extends HasParentNode {
computed: boolean
object: Expression | Super
property: Expression
+ optional: boolean
+}
+export interface ChainExpression extends HasParentNode {
+ type: 'ChainExpression'
+ expression: ChainElement
}
+export type ChainElement = CallExpression | MemberExpression
export interface YieldExpression extends HasParentNode {
type: 'YieldExpression'
delegate: boolean
@@ -457,6 +472,7 @@ export interface TemplateElement extends HasParentNode {
tail: boolean
value: {
cooked: string
+ // cooked: string | null // If the template literal is tagged and the text has an invalid escape, `cooked` will be `null`, e.g., `` tag`\unicode and \u{55}` ``
raw: string
}
}