diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js
index 7ac0245e8..6bad7e4b7 100644
--- a/lib/rules/comma-spacing.js
+++ b/lib/rules/comma-spacing.js
@@ -8,5 +8,6 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('comma-spacing', {
skipDynamicArguments: true,
- skipDynamicArgumentsReport: true
+ skipDynamicArgumentsReport: true,
+ applyDocument: true
})
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index 712658967..bd72bd828 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -6,4 +6,6 @@
const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('dot-notation')
+module.exports = wrapCoreRule('dot-notation', {
+ applyDocument: true
+})
diff --git a/lib/rules/eqeqeq.js b/lib/rules/eqeqeq.js
index 39894e1fc..d918b4d80 100644
--- a/lib/rules/eqeqeq.js
+++ b/lib/rules/eqeqeq.js
@@ -6,4 +6,6 @@
const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('eqeqeq')
+module.exports = wrapCoreRule('eqeqeq', {
+ applyDocument: true
+})
diff --git a/lib/rules/func-call-spacing.js b/lib/rules/func-call-spacing.js
index 9fb96ab39..2054376c0 100644
--- a/lib/rules/func-call-spacing.js
+++ b/lib/rules/func-call-spacing.js
@@ -7,5 +7,6 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('func-call-spacing', {
- skipDynamicArguments: true
+ skipDynamicArguments: true,
+ applyDocument: true
})
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 20c25938e..223bd630d 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -9,6 +9,7 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('no-extra-parens', {
skipDynamicArguments: true,
+ applyDocument: true,
create: createForVueSyntax
})
diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js
index a09ff6660..19feb14ef 100644
--- a/lib/rules/no-restricted-syntax.js
+++ b/lib/rules/no-restricted-syntax.js
@@ -6,4 +6,6 @@
const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('no-restricted-syntax')
+module.exports = wrapCoreRule('no-restricted-syntax', {
+ applyDocument: true
+})
diff --git a/lib/rules/no-useless-concat.js b/lib/rules/no-useless-concat.js
index 7379a1ee7..f2c51b908 100644
--- a/lib/rules/no-useless-concat.js
+++ b/lib/rules/no-useless-concat.js
@@ -6,4 +6,6 @@
const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('no-useless-concat')
+module.exports = wrapCoreRule('no-useless-concat', {
+ applyDocument: true
+})
diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js
index 5cc11e2a5..6fac1a059 100644
--- a/lib/rules/prefer-template.js
+++ b/lib/rules/prefer-template.js
@@ -6,4 +6,6 @@
const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
-module.exports = wrapCoreRule('prefer-template')
+module.exports = wrapCoreRule('prefer-template', {
+ applyDocument: true
+})
diff --git a/lib/rules/space-in-parens.js b/lib/rules/space-in-parens.js
index 29ba0403b..3a9a7e0dc 100644
--- a/lib/rules/space-in-parens.js
+++ b/lib/rules/space-in-parens.js
@@ -8,5 +8,6 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('space-in-parens', {
skipDynamicArguments: true,
- skipDynamicArgumentsReport: true
+ skipDynamicArgumentsReport: true,
+ applyDocument: true
})
diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js
index 650b215e8..dd0a8af26 100644
--- a/lib/rules/space-infix-ops.js
+++ b/lib/rules/space-infix-ops.js
@@ -7,5 +7,6 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('space-infix-ops', {
- skipDynamicArguments: true
+ skipDynamicArguments: true,
+ applyDocument: true
})
diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js
index 4920ca9e4..e3ece4c2f 100644
--- a/lib/rules/space-unary-ops.js
+++ b/lib/rules/space-unary-ops.js
@@ -7,5 +7,6 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('space-unary-ops', {
- skipDynamicArguments: true
+ skipDynamicArguments: true,
+ applyDocument: true
})
diff --git a/lib/rules/template-curly-spacing.js b/lib/rules/template-curly-spacing.js
index acefea6b5..549c790b6 100644
--- a/lib/rules/template-curly-spacing.js
+++ b/lib/rules/template-curly-spacing.js
@@ -7,5 +7,6 @@ const { wrapCoreRule } = require('../utils')
// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
module.exports = wrapCoreRule('template-curly-spacing', {
- skipDynamicArguments: true
+ skipDynamicArguments: true,
+ applyDocument: true
})
diff --git a/lib/utils/index.js b/lib/utils/index.js
index b0c189592..510e016a5 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -78,19 +78,24 @@ function getCoreRule(name) {
* Wrap the rule context object to override methods which access to tokens (such as getTokenAfter).
* @param {RuleContext} context The rule context object.
* @param {ParserServices.TokenStore} tokenStore The token store object for template.
+ * @param {Object} options The option of this rule.
+ * @param {boolean} [options.applyDocument] If `true`, apply check to document fragment.
* @returns {RuleContext}
*/
-function wrapContextToOverrideTokenMethods(context, tokenStore) {
+function wrapContextToOverrideTokenMethods(context, tokenStore, options) {
const eslintSourceCode = context.getSourceCode()
+ const rootNode = options.applyDocument
+ ? context.parserServices.getDocumentFragment &&
+ context.parserServices.getDocumentFragment()
+ : eslintSourceCode.ast.templateBody
/** @type {Token[] | null} */
let tokensAndComments = null
function getTokensAndComments() {
if (tokensAndComments) {
return tokensAndComments
}
- const templateBody = eslintSourceCode.ast.templateBody
- tokensAndComments = templateBody
- ? tokenStore.getTokens(templateBody, {
+ tokensAndComments = rootNode
+ ? tokenStore.getTokens(rootNode, {
includeComments: true
})
: []
@@ -99,8 +104,7 @@ function wrapContextToOverrideTokenMethods(context, tokenStore) {
/** @param {number} index */
function getNodeByRangeIndex(index) {
- const templateBody = eslintSourceCode.ast.templateBody
- if (!templateBody) {
+ if (!rootNode) {
return eslintSourceCode.ast
}
@@ -110,7 +114,7 @@ function wrapContextToOverrideTokenMethods(context, tokenStore) {
const skipNodes = []
let breakFlag = false
- traverseNodes(templateBody, {
+ traverseNodes(rootNode, {
enterNode(node, parent) {
if (breakFlag) {
return
@@ -242,6 +246,7 @@ module.exports = {
* @param {string[]} [options.categories] The categories of this rule.
* @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
* @param {boolean} [options.skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments.
+ * @param {boolean} [options.applyDocument] If `true`, apply check to document fragment.
* @param { (context: RuleContext, options: { coreHandlers: RuleListener }) => TemplateListener } [options.create] If define, extend core rule.
* @returns {RuleModule} The wrapped rule implementation.
*/
@@ -251,6 +256,7 @@ module.exports = {
categories,
skipDynamicArguments,
skipDynamicArgumentsReport,
+ applyDocument,
create
} = options || {}
return {
@@ -262,7 +268,9 @@ module.exports = {
// The `context.getSourceCode()` cannot access the tokens of templates.
// So override the methods which access to tokens by the `tokenStore`.
if (tokenStore) {
- context = wrapContextToOverrideTokenMethods(context, tokenStore)
+ context = wrapContextToOverrideTokenMethods(context, tokenStore, {
+ applyDocument
+ })
}
if (skipDynamicArgumentsReport) {
@@ -277,12 +285,19 @@ module.exports = {
Object.assign({}, coreHandlers)
)
if (handlers.Program) {
- handlers["VElement[parent.type!='VElement']"] = handlers.Program
+ handlers[
+ applyDocument
+ ? 'VDocumentFragment'
+ : "VElement[parent.type!='VElement']"
+ ] = /** @type {any} */ (handlers.Program)
delete handlers.Program
}
if (handlers['Program:exit']) {
- handlers["VElement[parent.type!='VElement']:exit"] =
- handlers['Program:exit']
+ handlers[
+ applyDocument
+ ? 'VDocumentFragment:exit'
+ : "VElement[parent.type!='VElement']:exit"
+ ] = /** @type {any} */ (handlers['Program:exit'])
delete handlers['Program:exit']
}
@@ -309,6 +324,10 @@ module.exports = {
compositingVisitors(handlers, create(context, { coreHandlers }))
}
+ if (applyDocument) {
+ // Apply the handlers to document.
+ return defineDocumentVisitor(context, handlers)
+ }
// Apply the handlers to templates.
return defineTemplateBodyVisitor(context, handlers)
},
@@ -1812,6 +1831,30 @@ function defineTemplateBodyVisitor(
options
)
}
+/**
+ * Register the given visitor to parser services.
+ * If the parser service of `vue-eslint-parser` was not found,
+ * this generates a warning.
+ *
+ * @param {RuleContext} context The rule context to use parser services.
+ * @param {TemplateListener} documentVisitor The visitor to traverse the document.
+ * @param { { triggerSelector: "Program" | "Program:exit" } } [options] The options.
+ * @returns {RuleListener} The merged visitor.
+ */
+function defineDocumentVisitor(context, documentVisitor, options) {
+ if (context.parserServices.defineDocumentVisitor == null) {
+ const filename = context.getFilename()
+ if (path.extname(filename) === '.vue') {
+ context.report({
+ loc: { line: 1, column: 0 },
+ message:
+ 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.'
+ })
+ }
+ return {}
+ }
+ return context.parserServices.defineDocumentVisitor(documentVisitor, options)
+}
/**
* @template T
diff --git a/package.json b/package.json
index d55714151..7ceec76c6 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"eslint-utils": "^2.1.0",
"natural-compare": "^1.4.0",
"semver": "^6.3.0",
- "vue-eslint-parser": "^7.9.0"
+ "vue-eslint-parser": "^7.10.0"
},
"devDependencies": {
"@types/eslint": "^7.2.0",
diff --git a/tests/lib/rules/comma-spacing.js b/tests/lib/rules/comma-spacing.js
index e5b74407f..68b70b324 100644
--- a/tests/lib/rules/comma-spacing.js
+++ b/tests/lib/rules/comma-spacing.js
@@ -61,7 +61,14 @@ tester.run('comma-spacing', rule, {
``,
- `fn = (a,b) => {}`
+ `fn = (a,b) => {}`,
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -278,6 +285,26 @@ tester.run('comma-spacing', rule, {
line: 4
}
]
+ },
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [
+ {
+ message: "A space is required after ','.",
+ line: 4
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/dot-notation.js b/tests/lib/rules/dot-notation.js
index eea4c1928..ff32b85ef 100644
--- a/tests/lib/rules/dot-notation.js
+++ b/tests/lib/rules/dot-notation.js
@@ -17,7 +17,14 @@ tester.run('dot-notation', rule, {
'',
``,
``,
- ``
+ ``,
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -29,6 +36,37 @@ tester.run('dot-notation', rule, {
code: ``,
output: ``,
errors: ['[`bar`] is better written in dot notation.']
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: ['[`bar`] is better written in dot notation.']
+ },
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: ['[`bar`] is better written in dot notation.']
}
]
})
diff --git a/tests/lib/rules/eqeqeq.js b/tests/lib/rules/eqeqeq.js
index baa6d4b6c..092c6f0d5 100644
--- a/tests/lib/rules/eqeqeq.js
+++ b/tests/lib/rules/eqeqeq.js
@@ -12,11 +12,30 @@ const tester = new RuleTester({
})
tester.run('eqeqeq', rule, {
- valid: [''],
+ valid: [
+ '',
+ // CSS vars injection
+ `
+ `
+ ],
invalid: [
{
code: '',
errors: ["Expected '===' and instead saw '=='."]
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ errors: ["Expected '===' and instead saw '=='."]
}
]
})
diff --git a/tests/lib/rules/func-call-spacing.js b/tests/lib/rules/func-call-spacing.js
index 93df1577c..d92502006 100644
--- a/tests/lib/rules/func-call-spacing.js
+++ b/tests/lib/rules/func-call-spacing.js
@@ -39,7 +39,14 @@ tester.run('func-call-spacing', rule, {
`,
options: ['always']
+ },
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -80,6 +87,30 @@ tester.run('func-call-spacing', rule, {
line: 3
}
]
+ },
+
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [
+ {
+ message: semver.lt(CLIEngine.version, '7.0.0')
+ ? 'Unexpected newline between function name and paren.'
+ : 'Unexpected whitespace between function name and paren.',
+ line: 4
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 1bb704bac..28f644a90 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -41,7 +41,14 @@ tester.run('no-extra-parens', rule, {
'',
'',
'',
- ''
+ '',
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -192,6 +199,22 @@ tester.run('no-extra-parens', rule, {
code: '',
output: '',
errors: [{ messageId: 'unexpected' }]
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [{ messageId: 'unexpected' }]
}
]
})
diff --git a/tests/lib/rules/no-restricted-syntax.js b/tests/lib/rules/no-restricted-syntax.js
index 662c2296f..647bdc4b6 100644
--- a/tests/lib/rules/no-restricted-syntax.js
+++ b/tests/lib/rules/no-restricted-syntax.js
@@ -50,6 +50,21 @@ tester.run('no-restricted-syntax', rule, {
message: 'Third argument of interpolate must be true'
}
]
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ options: [
+ {
+ selector: 'CallExpression',
+ message: 'Call expressions are not allowed.'
+ }
+ ]
}
],
invalid: [
@@ -183,6 +198,28 @@ tester.run('no-restricted-syntax', rule, {
column: 48
}
]
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ options: [
+ {
+ selector: 'CallExpression',
+ message: 'Call expressions are not allowed.'
+ }
+ ],
+ errors: [
+ {
+ message: 'Call expressions are not allowed.',
+ line: 4,
+ column: 24
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/no-useless-concat.js b/tests/lib/rules/no-useless-concat.js
index 72e1ca551..28ac8240d 100644
--- a/tests/lib/rules/no-useless-concat.js
+++ b/tests/lib/rules/no-useless-concat.js
@@ -15,7 +15,14 @@ tester.run('no-useless-concat', rule, {
valid: [
``,
'',
- ``
+ ``,
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -25,6 +32,16 @@ tester.run('no-useless-concat', rule, {
{
code: ``,
errors: ['Unexpected string concatenation of literals.']
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ errors: ['Unexpected string concatenation of literals.']
}
]
})
diff --git a/tests/lib/rules/prefer-template.js b/tests/lib/rules/prefer-template.js
index 73ad5e5b6..e35fa6d1f 100644
--- a/tests/lib/rules/prefer-template.js
+++ b/tests/lib/rules/prefer-template.js
@@ -22,7 +22,14 @@ tester.run('prefer-template', rule, {
+ `,
+ // CSS vars injection
`
+ `
],
invalid: [
{
@@ -58,6 +65,27 @@ tester.run('prefer-template', rule, {
line: 3
}
]
+ },
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [
+ {
+ message: 'Unexpected string concatenation.',
+ line: 4
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/space-in-parens.js b/tests/lib/rules/space-in-parens.js
index 421fb4e95..57be0b52e 100644
--- a/tests/lib/rules/space-in-parens.js
+++ b/tests/lib/rules/space-in-parens.js
@@ -53,7 +53,14 @@ tester.run('space-in-parens', rule, {
/>
`,
options: ['always']
+ },
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -202,6 +209,32 @@ tester.run('space-in-parens', rule, {
line: 4
})
]
+ },
+
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [
+ errorMessage({
+ messageId: 'rejectedOpeningSpace',
+ line: 4
+ }),
+ errorMessage({
+ messageId: 'rejectedClosingSpace',
+ line: 4
+ })
+ ]
}
]
})
diff --git a/tests/lib/rules/space-infix-ops.js b/tests/lib/rules/space-infix-ops.js
index 427a8fdc5..26b008d86 100644
--- a/tests/lib/rules/space-infix-ops.js
+++ b/tests/lib/rules/space-infix-ops.js
@@ -20,7 +20,15 @@ tester.run('space-infix-ops', rule, {
valid: [
'',
'',
- ''
+ '',
+
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -42,6 +50,23 @@ tester.run('space-infix-ops', rule, {
code: '',
output: '',
errors: [message('+')]
+ },
+
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [message('+'), message('+')]
}
]
})
diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js
index 9d74207c9..6b3e7c25f 100644
--- a/tests/lib/rules/space-unary-ops.js
+++ b/tests/lib/rules/space-unary-ops.js
@@ -23,7 +23,14 @@ tester.run('space-unary-ops', rule, {
{
code: '',
options: [{ nonwords: true }]
+ },
+ // CSS vars injection
+ `
+ `
],
invalid: [
{
@@ -46,6 +53,23 @@ tester.run('space-unary-ops', rule, {
options: [{ nonwords: true }],
output: '',
errors: ["Unary operator '!' must be followed by whitespace."]
+ },
+
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: ["Unexpected space after unary operator '-'."]
}
]
})
diff --git a/tests/lib/rules/template-curly-spacing.js b/tests/lib/rules/template-curly-spacing.js
index 02aeb43e5..6c4376969 100644
--- a/tests/lib/rules/template-curly-spacing.js
+++ b/tests/lib/rules/template-curly-spacing.js
@@ -38,6 +38,16 @@ tester.run('template-curly-spacing', rule, {
`,
options: ['always']
+ },
+
+ // CSS vars injection
+ {
+ code: `
+ `
}
],
invalid: [
@@ -85,6 +95,32 @@ tester.run('template-curly-spacing', rule, {
line: 3
}
]
+ },
+
+ // CSS vars injection
+ {
+ code: `
+ `,
+ output: `
+ `,
+ errors: [
+ {
+ message: "Unexpected space(s) after '${'.",
+ line: 4
+ },
+ {
+ message: "Unexpected space(s) before '}'.",
+ line: 4
+ }
+ ]
}
]
})
diff --git a/typings/eslint-plugin-vue/util-types/ast/ast.ts b/typings/eslint-plugin-vue/util-types/ast/ast.ts
index e29c199fe..4356ebc55 100644
--- a/typings/eslint-plugin-vue/util-types/ast/ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/ast.ts
@@ -145,6 +145,8 @@ export type VNodeListenerMap = {
'VFilterSequenceExpression:exit': V.VFilterSequenceExpression
VFilter: V.VFilter
'VFilter:exit': V.VFilter
+ VDocumentFragment: V.VDocumentFragment
+ 'VDocumentFragment:exit': V.VDocumentFragment
} & ESNodeListenerMap
export type NodeListenerMap = {
JSXAttribute: JSX.JSXAttribute
diff --git a/typings/eslint-plugin-vue/util-types/parser-services.ts b/typings/eslint-plugin-vue/util-types/parser-services.ts
index 183516770..4c938f33d 100644
--- a/typings/eslint-plugin-vue/util-types/parser-services.ts
+++ b/typings/eslint-plugin-vue/util-types/parser-services.ts
@@ -20,6 +20,12 @@ export interface ParserServices {
templateBodyTriggerSelector: 'Program' | 'Program:exit'
}
) => eslint.Rule.RuleListener
+ defineDocumentVisitor?: (
+ documentVisitor: TemplateListener,
+ options?: {
+ triggerSelector: 'Program' | 'Program:exit'
+ }
+ ) => eslint.Rule.RuleListener
getDocumentFragment?: () => VAST.VDocumentFragment | null
}
export namespace ParserServices {