diff --git a/.github/workflows/check-for-resources-update.yml b/.github/workflows/check-for-resources-update.yml
index 613125fc3..31f881b1d 100644
--- a/.github/workflows/check-for-resources-update.yml
+++ b/.github/workflows/check-for-resources-update.yml
@@ -5,12 +5,13 @@ on:
- cron: 0 0 * * 0 # At 00:00 on Sunday, see https://crontab.guru/#0_0_*_*_0
permissions:
- contents: write
- pull-requests: write
+ contents: write
+ pull-requests: write
jobs:
check-for-resources-update:
runs-on: ubuntu-latest
+ if: ${{ github.repository == 'vuejs/eslint-plugin-vue' }}
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/README.md b/README.md
index 95e0c08be..bdeb5e167 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](https://npmjs.org/package/eslint-plugin-vue)
[](https://npmjs.org/package/eslint-plugin-vue)
-[](https://circleci.com/gh/vuejs/eslint-plugin-vue)
+[](https://github.com/vuejs/eslint-plugin-vue/actions/workflows/CI.yml)
[](https://github.com/vuejs/eslint-plugin-vue/blob/master/LICENSE)
> Official ESLint plugin for Vue.js
diff --git a/docs/rules/define-emits-declaration.md b/docs/rules/define-emits-declaration.md
index 7a11524c5..c24f5ec62 100644
--- a/docs/rules/define-emits-declaration.md
+++ b/docs/rules/define-emits-declaration.md
@@ -35,8 +35,8 @@ const emit = defineEmits<{
/* ✗ BAD */
const emit = defineEmits({
- change: (id) => typeof id == 'number',
- update: (value) => typeof value == 'string'
+ change: (id) => typeof id === 'number',
+ update: (value) => typeof value === 'string'
})
/* ✗ BAD */
@@ -70,8 +70,8 @@ const emit = defineEmits<{
/* ✓ GOOD */
const emit = defineEmits({
- change: (id) => typeof id == 'number',
- update: (value) => typeof value == 'string'
+ change: (id) => typeof id === 'number',
+ update: (value) => typeof value === 'string'
})
/* ✓ GOOD */
@@ -92,8 +92,8 @@ const emit = defineEmits(['change', 'update'])
/* ✗ BAD */
const emit = defineEmits({
- change: (id) => typeof id == 'number',
- update: (value) => typeof value == 'string'
+ change: (id) => typeof id === 'number',
+ update: (value) => typeof value === 'string'
})
/* ✗ BAD */
diff --git a/docs/rules/define-props-destructuring.md b/docs/rules/define-props-destructuring.md
new file mode 100644
index 000000000..e3c2b2745
--- /dev/null
+++ b/docs/rules/define-props-destructuring.md
@@ -0,0 +1,98 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-props-destructuring
+description: enforce consistent style for props destructuring
+since: v10.1.0
+---
+
+# vue/define-props-destructuring
+
+> enforce consistent style for props destructuring
+
+## :book: Rule Details
+
+This rule enforces a consistent style for handling Vue 3 Composition API props, allowing you to choose between requiring destructuring or prohibiting it.
+
+By default, the rule requires you to use destructuring syntax when using `defineProps` instead of storing props in a variable and warns against combining `withDefaults` with destructuring.
+
+
+
+```vue
+
+```
+
+
+
+The rule applies to both JavaScript and TypeScript props:
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```js
+{
+ "vue/define-props-destructuring": ["error", {
+ "destructure": "always" | "never"
+ }]
+}
+```
+
+- `destructure` - Sets the destructuring preference for props
+ - `"always"` (default) - Requires destructuring when using `defineProps` and warns against using `withDefaults` with destructuring
+ - `"never"` - Requires using a variable to store props and prohibits destructuring
+
+### `"destructure": "never"`
+
+
+
+```vue
+
+```
+
+
+
+## :books: Further Reading
+
+- [Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure)
+
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v10.1.0
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-destructuring.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-destructuring.js)
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 55c5b96c9..7828b58bb 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -218,6 +218,7 @@ For example:
| [vue/define-emits-declaration] | enforce declaration style of `defineEmits` | | :hammer: |
| [vue/define-macros-order] | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: |
| [vue/define-props-declaration] | enforce declaration style of `defineProps` | | :hammer: |
+| [vue/define-props-destructuring] | enforce consistent style for props destructuring | | :hammer: |
| [vue/enforce-style-attribute] | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: |
| [vue/html-button-has-type] | disallow usage of button without an explicit type attribute | | :hammer: |
| [vue/html-comment-content-newline] | enforce unified line break in HTML comments | :wrench: | :lipstick: |
@@ -398,6 +399,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
[vue/define-emits-declaration]: ./define-emits-declaration.md
[vue/define-macros-order]: ./define-macros-order.md
[vue/define-props-declaration]: ./define-props-declaration.md
+[vue/define-props-destructuring]: ./define-props-destructuring.md
[vue/dot-location]: ./dot-location.md
[vue/dot-notation]: ./dot-notation.md
[vue/enforce-style-attribute]: ./enforce-style-attribute.md
diff --git a/docs/rules/no-bare-strings-in-template.md b/docs/rules/no-bare-strings-in-template.md
index df1fae123..23a23c116 100644
--- a/docs/rules/no-bare-strings-in-template.md
+++ b/docs/rules/no-bare-strings-in-template.md
@@ -12,7 +12,7 @@ since: v7.0.0
## :book: Rule Details
-This rule disallows the use of bare strings in ``.
+This rule disallows the use of bare strings in ``.
In order to be able to internationalize your application, you will need to avoid using plain strings in your templates. Instead, you would need to use a template helper specializing in translation.
This rule was inspired by [no-bare-strings rule in ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-bare-strings.md).
@@ -50,7 +50,7 @@ This rule was inspired by [no-bare-strings rule in ember-template-lint](https://
:::tip
-This rule does not check for string literals, in bindings and mustaches interpolation. This is because it looks like a conscious decision.
+This rule does not check for string literals, in bindings and mustaches interpolation. This is because it looks like a conscious decision.
If you want to report these string literals, enable the [vue/no-useless-v-bind] and [vue/no-useless-mustaches] rules and fix the useless string literals.
:::
@@ -72,7 +72,7 @@ If you want to report these string literals, enable the [vue/no-useless-v-bind]
}
```
-- `allowlist` ... An array of allowed strings.
+- `allowlist` ... An array of allowed strings or regular expression patterns (e.g. `/\d+/` to allow numbers).
- `attributes` ... An object whose keys are tag name or patterns and value is an array of attributes to check for that tag name.
- `directives` ... An array of directive names to check literal value.
diff --git a/docs/rules/no-multiple-template-root.md b/docs/rules/no-multiple-template-root.md
index 90b5488fc..8a85c9201 100644
--- a/docs/rules/no-multiple-template-root.md
+++ b/docs/rules/no-multiple-template-root.md
@@ -61,7 +61,32 @@ This rule checks whether template contains single root element valid for Vue 2.
## :wrench: Options
-Nothing.
+```json
+{
+ "vue/no-multiple-template-root": ["error", {
+ "disallowComments": false
+ }]
+}
+```
+
+- "disallowComments" (`boolean`) Enables there should not be any comments in the template root. Default is `false`.
+
+### "disallowComments": true
+
+
+
+```vue
+/* ✗ BAD */
+
+
+
+ vue eslint plugin
+
+
+
+```
+
+
## :rocket: Version
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
index fcd7ff45c..103471f61 100644
--- a/docs/user-guide/index.md
+++ b/docs/user-guide/index.md
@@ -11,7 +11,7 @@ npm install --save-dev eslint eslint-plugin-vue
Via [yarn](https://yarnpkg.com/):
```bash
-yarn add -D eslint eslint-plugin-vue globals
+yarn add -D eslint eslint-plugin-vue vue-eslint-parser globals
```
::: tip Requirements
@@ -166,8 +166,8 @@ module.exports = {
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
- 'plugin:vue/vue3-recommended',
- // 'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.
+ 'plugin:vue/recommended',
+ // 'plugin:vue/vue2-recommended' // Use this if you are using Vue.js 2.x.
],
rules: {
// override/add rules settings here, such as:
@@ -185,13 +185,13 @@ You can use the following configs by adding them to `extends`.
- `"plugin:vue/base"` ... Settings and rules to enable correct ESLint parsing.
- Configurations for using Vue.js 3.x:
- - `"plugin:vue/vue3-essential"` ... `base`, plus rules to prevent errors or unintended behavior.
- - `"plugin:vue/vue3-strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience.
- - `"plugin:vue/vue3-recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
-- Configurations for using Vue.js 2.x:
- `"plugin:vue/essential"` ... `base`, plus rules to prevent errors or unintended behavior.
- `"plugin:vue/strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience.
- - `"plugin:vue/recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency
+ - `"plugin:vue/recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
+- Configurations for using Vue.js 2.x:
+ - `"plugin:vue/vue2-essential"` ... `base`, plus rules to prevent errors or unintended behavior.
+ - `"plugin:vue/vue2-strongly-recommended"` ... Above, plus rules to considerably improve code readability and/or dev experience.
+ - `"plugin:vue/vue2-recommended"` ... Above, plus rules to enforce subjective community defaults to ensure consistency.
:::warning Reporting rules
By default, all rules from **base** and **essential** categories report ESLint errors. Other rules - because they're not covering potential bugs in the application - report warnings. What does it mean? By default - nothing, but if you want - you can set up a threshold and break the build after a certain amount of warnings, instead of any. More information [here](https://eslint.org/docs/user-guide/command-line-interface#handling-warnings).
@@ -264,7 +264,7 @@ Full example:
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
- "plugin:vue/vue3-recommended"
+ "plugin:vue/recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
@@ -436,7 +436,7 @@ Most `eslint-plugin-vue` rules require `vue-eslint-parser` to check ``
Make sure you have one of the following settings in your **.eslintrc**:
-- `"extends": ["plugin:vue/vue3-recommended"]`
+- `"extends": ["plugin:vue/recommended"]`
- `"extends": ["plugin:vue/base"]`
If you already use another parser (e.g. `"parser": "@typescript-eslint/parser"`), please move it into `parserOptions`, so it doesn't collide with the `vue-eslint-parser` used by this plugin's configuration:
@@ -482,7 +482,7 @@ module.exports = {
// ...
// 'eslint:recommended',
// ...
- 'plugin:vue/vue3-recommended',
+ 'plugin:vue/recommended',
// ...
'prettier'
// Make sure "prettier" is the last element in this list.
diff --git a/lib/index.d.ts b/lib/index.d.ts
index 19bbc8a9d..8cbff659f 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -9,9 +9,9 @@ declare const vue: {
'vue2-strongly-recommended': Linter.LegacyConfig
'vue2-recommended': Linter.LegacyConfig
- 'vue3-essential': Linter.LegacyConfig
- 'vue3-strongly-recommended': Linter.LegacyConfig
- 'vue3-recommended': Linter.LegacyConfig
+ essential: Linter.LegacyConfig
+ 'strongly-recommended': Linter.LegacyConfig
+ recommended: Linter.LegacyConfig
'flat/base': Linter.FlatConfig[]
diff --git a/lib/index.js b/lib/index.js
index 834e5f28b..e511536fa 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -58,6 +58,7 @@ const plugin = {
'define-emits-declaration': require('./rules/define-emits-declaration'),
'define-macros-order': require('./rules/define-macros-order'),
'define-props-declaration': require('./rules/define-props-declaration'),
+ 'define-props-destructuring': require('./rules/define-props-destructuring'),
'dot-location': require('./rules/dot-location'),
'dot-notation': require('./rules/dot-notation'),
'enforce-style-attribute': require('./rules/enforce-style-attribute'),
diff --git a/lib/rules/define-props-destructuring.js b/lib/rules/define-props-destructuring.js
new file mode 100644
index 000000000..65ec1dcd7
--- /dev/null
+++ b/lib/rules/define-props-destructuring.js
@@ -0,0 +1,79 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce consistent style for props destructuring',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ destructure: {
+ enum: ['always', 'never']
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ preferDestructuring: 'Prefer destructuring from `defineProps` directly.',
+ avoidDestructuring: 'Avoid destructuring from `defineProps`.',
+ avoidWithDefaults: 'Avoid using `withDefaults` with destructuring.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0] || {}
+ const destructurePreference = options.destructure || 'always'
+
+ return utils.compositingVisitors(
+ utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node, props) {
+ const hasNoArgs = props.filter((prop) => prop.propName).length === 0
+ if (hasNoArgs) {
+ return
+ }
+
+ const hasDestructure = utils.isUsingPropsDestructure(node)
+ const hasWithDefaults = utils.hasWithDefaults(node)
+
+ if (destructurePreference === 'never') {
+ if (hasDestructure) {
+ context.report({
+ node,
+ messageId: 'avoidDestructuring'
+ })
+ }
+ return
+ }
+
+ if (!hasDestructure) {
+ context.report({
+ node,
+ messageId: 'preferDestructuring'
+ })
+ return
+ }
+
+ if (hasWithDefaults) {
+ context.report({
+ node: node.parent.callee,
+ messageId: 'avoidWithDefaults'
+ })
+ }
+ }
+ })
+ )
+ }
+}
diff --git a/lib/rules/html-self-closing.js b/lib/rules/html-self-closing.js
index 5b98d8cf7..c31c9ab70 100644
--- a/lib/rules/html-self-closing.js
+++ b/lib/rules/html-self-closing.js
@@ -150,8 +150,7 @@ module.exports = {
isEmpty(node, sourceCode)
) {
context.report({
- node,
- loc: node.loc,
+ node: node.endTag || node,
messageId: 'requireSelfClosing',
data: {
elementType: ELEMENT_TYPE_MESSAGES[elementType],
@@ -175,7 +174,13 @@ module.exports = {
if (mode === 'never' && node.startTag.selfClosing) {
context.report({
node,
- loc: node.loc,
+ loc: {
+ start: {
+ line: node.loc.end.line,
+ column: node.loc.end.column - 2
+ },
+ end: node.loc.end
+ },
messageId: 'disallowSelfClosing',
data: {
elementType: ELEMENT_TYPE_MESSAGES[elementType],
diff --git a/lib/rules/no-bare-strings-in-template.js b/lib/rules/no-bare-strings-in-template.js
index 1c75f5092..8e4acc96d 100644
--- a/lib/rules/no-bare-strings-in-template.js
+++ b/lib/rules/no-bare-strings-in-template.js
@@ -149,17 +149,33 @@ module.exports = {
*/
const opts = context.options[0] || {}
/** @type {string[]} */
- const allowlist = opts.allowlist || DEFAULT_ALLOWLIST
+ const rawAllowlist = opts.allowlist || DEFAULT_ALLOWLIST
const attributes = parseTargetAttrs(opts.attributes || DEFAULT_ATTRIBUTES)
const directives = opts.directives || DEFAULT_DIRECTIVES
- const allowlistRe = new RegExp(
- allowlist
- .map((w) => regexp.escape(w))
- .sort((a, b) => b.length - a.length)
- .join('|'),
- 'gu'
- )
+ /** @type {string[]} */
+ const stringAllowlist = []
+ /** @type {RegExp[]} */
+ const regexAllowlist = []
+
+ for (const item of rawAllowlist) {
+ if (regexp.isRegExp(item)) {
+ regexAllowlist.push(regexp.toRegExp(item))
+ } else {
+ stringAllowlist.push(item)
+ }
+ }
+
+ const allowlistRe =
+ stringAllowlist.length > 0
+ ? new RegExp(
+ stringAllowlist
+ .map((w) => regexp.escape(w))
+ .sort((a, b) => b.length - a.length)
+ .join('|'),
+ 'gu'
+ )
+ : null
/** @type {ElementStack | null} */
let elementStack = null
@@ -168,7 +184,21 @@ module.exports = {
* @param {string} str
*/
function getBareString(str) {
- return str.trim().replace(allowlistRe, '').trim()
+ let result = str.trim()
+
+ if (allowlistRe) {
+ result = result.replace(allowlistRe, '')
+ }
+
+ for (const regex of regexAllowlist) {
+ const flags = regex.flags.includes('g')
+ ? regex.flags
+ : `${regex.flags}g`
+ const globalRegex = new RegExp(regex.source, flags)
+ result = result.replace(globalRegex, '')
+ }
+
+ return result.trim()
}
/**
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index 01b85d9f5..ecfa787cf 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -58,6 +58,33 @@ function isInsideInitializer(node, references) {
)
}
+/**
+ * Collects all renamed props from a pattern
+ * @param {Pattern | null} pattern - The destructuring pattern
+ * @returns {Set} - Set of prop names that have been renamed
+ */
+function collectRenamedProps(pattern) {
+ const renamedProps = new Set()
+
+ if (!pattern || pattern.type !== 'ObjectPattern') {
+ return renamedProps
+ }
+
+ for (const prop of pattern.properties) {
+ if (prop.type !== 'Property') continue
+
+ if (
+ prop.key.type === 'Identifier' &&
+ prop.value.type === 'Identifier' &&
+ prop.key.name !== prop.value.name
+ ) {
+ renamedProps.add(prop.key.name)
+ }
+ }
+
+ return renamedProps
+}
+
module.exports = {
meta: {
type: 'problem',
@@ -115,9 +142,15 @@ module.exports = {
node
]
+ const renamedProps = collectRenamedProps(propsNode)
+
for (const prop of props) {
if (!prop.propName) continue
+ if (renamedProps.has(prop.propName)) {
+ continue
+ }
+
const variable = findVariable(
utils.getScope(context, node),
prop.propName
diff --git a/lib/rules/no-duplicate-attr-inheritance.js b/lib/rules/no-duplicate-attr-inheritance.js
index 1f0fb0fd3..31aef7e44 100644
--- a/lib/rules/no-duplicate-attr-inheritance.js
+++ b/lib/rules/no-duplicate-attr-inheritance.js
@@ -63,7 +63,7 @@ module.exports = {
const options = context.options[0] || {}
const checkMultiRootNodes = options.checkMultiRootNodes === true
- /** @type {string | number | boolean | RegExp | BigInt | null} */
+ /** @type {Literal['value']} */
let inheritsAttrs = true
/** @type {VReference[]} */
const attrsRefs = []
diff --git a/lib/rules/no-export-in-script-setup.js b/lib/rules/no-export-in-script-setup.js
index 66286375a..98d41ae38 100644
--- a/lib/rules/no-export-in-script-setup.js
+++ b/lib/rules/no-export-in-script-setup.js
@@ -28,8 +28,11 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
- /** @param {ExportAllDeclaration | ExportDefaultDeclaration | ExportNamedDeclaration} node */
- function verify(node) {
+ /**
+ * @param {ExportAllDeclaration | ExportDefaultDeclaration | ExportNamedDeclaration} node
+ * @param {SourceLocation} loc
+ */
+ function verify(node, loc) {
const tsNode =
/** @type {TSESTreeExportAllDeclaration | TSESTreeExportDefaultDeclaration | TSESTreeExportNamedDeclaration} */ (
node
@@ -46,14 +49,24 @@ module.exports = {
}
context.report({
node,
+ loc,
messageId: 'forbidden'
})
}
return utils.defineScriptSetupVisitor(context, {
- ExportAllDeclaration: verify,
- ExportDefaultDeclaration: verify,
- ExportNamedDeclaration: verify
+ ExportAllDeclaration: (node) => verify(node, node.loc),
+ ExportDefaultDeclaration: (node) => verify(node, node.loc),
+ ExportNamedDeclaration: (node) => {
+ // export let foo = 'foo', export class Foo {}, export function foo() {}
+ if (node.declaration) {
+ verify(node, context.getSourceCode().getFirstToken(node).loc)
+ }
+ // export { foo }, export { foo } from 'bar'
+ else {
+ verify(node, node.loc)
+ }
+ }
})
}
}
diff --git a/lib/rules/no-multiple-template-root.js b/lib/rules/no-multiple-template-root.js
index 45c22389f..524b7a2b1 100644
--- a/lib/rules/no-multiple-template-root.js
+++ b/lib/rules/no-multiple-template-root.js
@@ -6,6 +6,21 @@
const utils = require('../utils')
+/**
+ * Get all comments that need to be reported
+ * @param {(HTMLComment | HTMLBogusComment | Comment)[]} comments
+ * @param {Range[]} elementRanges
+ * @returns {(HTMLComment | HTMLBogusComment | Comment)[]}
+ */
+function getReportComments(comments, elementRanges) {
+ return comments.filter(
+ (comment) =>
+ !elementRanges.some(
+ (range) => range[0] <= comment.range[0] && comment.range[1] <= range[1]
+ )
+ )
+}
+
module.exports = {
meta: {
type: 'problem',
@@ -15,8 +30,19 @@ module.exports = {
url: 'https://eslint.vuejs.org/rules/no-multiple-template-root.html'
},
fixable: null,
- schema: [],
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ disallowComments: {
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }
+ ],
messages: {
+ commentRoot: 'The template root disallows comments.',
multipleRoot: 'The template root requires exactly one element.',
textRoot: 'The template root requires an element rather than texts.',
disallowedElement: "The template root disallows '<{{name}}>' elements.",
@@ -28,6 +54,8 @@ module.exports = {
* @returns {RuleListener} AST event handlers.
*/
create(context) {
+ const options = context.options[0] || {}
+ const disallowComments = options.disallowComments
const sourceCode = context.getSourceCode()
return {
@@ -37,6 +65,18 @@ module.exports = {
return
}
+ const comments = element.comments
+ const elementRanges = element.children.map((child) => child.range)
+ if (disallowComments && comments.length > 0) {
+ for (const comment of getReportComments(comments, elementRanges)) {
+ context.report({
+ node: comment,
+ loc: comment.loc,
+ messageId: 'commentRoot'
+ })
+ }
+ }
+
const rootElements = []
let extraText = null
let extraElement = null
diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js
index db92f19e4..b1a9e12a8 100644
--- a/lib/rules/no-ref-as-operand.js
+++ b/lib/rules/no-ref-as-operand.js
@@ -233,7 +233,7 @@ module.exports = {
},
// `${refValue}`
/** @param {Identifier} node */
- 'TemplateLiteral>Identifier'(node) {
+ ':not(TaggedTemplateExpression)>TemplateLiteral>Identifier'(node) {
reportIfRefWrapped(node)
},
// refValue.x
diff --git a/lib/rules/no-unused-refs.js b/lib/rules/no-unused-refs.js
index 4896ce25a..6c5407e02 100644
--- a/lib/rules/no-unused-refs.js
+++ b/lib/rules/no-unused-refs.js
@@ -237,8 +237,10 @@ module.exports = {
CallExpression(callExpression) {
const firstArgument = callExpression.arguments[0]
if (
+ callExpression.callee.type !== 'Identifier' ||
callExpression.callee.name !== 'useTemplateRef' ||
- !firstArgument
+ !firstArgument ||
+ !utils.isStringLiteral(firstArgument)
) {
return
}
diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js
index b5d2564e8..acceacf08 100644
--- a/lib/rules/require-default-prop.js
+++ b/lib/rules/require-default-prop.js
@@ -200,8 +200,8 @@ module.exports = {
processProps(props, (prop) => {
if (prop.type === 'type') {
- if (!hasWithDefaults) {
- // If don't use withDefaults(), exclude it from the report.
+ if (!hasWithDefaults && !isUsingPropsDestructure) {
+ // If don't use withDefaults() and props destructure, exclude it from the report.
return true
}
if (defaultsByWithDefaults[prop.propName]) {
diff --git a/lib/utils/html-elements.json b/lib/utils/html-elements.json
index 829b6f841..ff9cdf313 100644
--- a/lib/utils/html-elements.json
+++ b/lib/utils/html-elements.json
@@ -74,7 +74,6 @@
"output",
"p",
"picture",
- "portal",
"pre",
"progress",
"q",
@@ -87,6 +86,7 @@
"search",
"section",
"select",
+ "selectedcontent",
"slot",
"small",
"source",
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 8d0dfa80d..769362966 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -1380,7 +1380,7 @@ module.exports = {
* @param {any[]} args
*/
function callVisitor(key, node, ...args) {
- if (visitor[key] && inScriptSetup(node)) {
+ if (visitor[key] && (node.type === 'Program' || inScriptSetup(node))) {
// @ts-expect-error
visitor[key](node, ...args)
}
diff --git a/lib/utils/svg-elements.json b/lib/utils/svg-elements.json
index f214aad24..eedbf0d07 100644
--- a/lib/utils/svg-elements.json
+++ b/lib/utils/svg-elements.json
@@ -7,6 +7,7 @@
"clipPath",
"defs",
"desc",
+ "discard",
"ellipse",
"feBlend",
"feColorMatrix",
diff --git a/package.json b/package.json
index 475c24e60..7e44aeb84 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-vue",
- "version": "10.0.0",
+ "version": "10.1.0",
"description": "Official ESLint plugin for Vue.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -69,7 +69,6 @@
"@ota-meshi/site-kit-eslint-editor-vue": "^0.2.4",
"@stylistic/eslint-plugin": "^2.12.1",
"@types/eslint": "^8.56.2",
- "@types/eslint-visitor-keys": "^3.3.2",
"@types/natural-compare": "^1.4.3",
"@types/node": "^14.18.63",
"@types/semver": "^7.5.8",
@@ -88,6 +87,7 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unicorn": "^56.0.0",
"eslint-plugin-vue": "file:.",
+ "eslint-visitor-keys": "^4.2.0",
"espree": "^9.6.1",
"events": "^3.3.0",
"globals": "^15.14.0",
diff --git a/tests/lib/rules/define-props-destructuring.js b/tests/lib/rules/define-props-destructuring.js
new file mode 100644
index 000000000..ec24b4328
--- /dev/null
+++ b/tests/lib/rules/define-props-destructuring.js
@@ -0,0 +1,212 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/define-props-destructuring')
+
+const tester = new RuleTester({
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ecmaVersion: 2015,
+ sourceType: 'module'
+ }
+})
+
+tester.run('define-props-destructuring', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ destructure: 'never' }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ destructure: 'never' }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ destructure: 'never' }],
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'preferDestructuring',
+ line: 3,
+ column: 21
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'preferDestructuring',
+ line: 3,
+ column: 34
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'avoidWithDefaults',
+ line: 3,
+ column: 23
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'preferDestructuring',
+ line: 3,
+ column: 34
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'avoidWithDefaults',
+ line: 3,
+ column: 23
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ destructure: 'never' }],
+ errors: [
+ {
+ messageId: 'avoidDestructuring',
+ line: 3,
+ column: 23
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ destructure: 'never' }],
+ errors: [
+ {
+ messageId: 'avoidDestructuring',
+ line: 3,
+ column: 36
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [{ destructure: 'never' }],
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'avoidDestructuring',
+ line: 3,
+ column: 23
+ }
+ ]
+ }
+ ]
+})
diff --git a/tests/lib/rules/no-bare-strings-in-template.js b/tests/lib/rules/no-bare-strings-in-template.js
index 4c6c89c7c..6706aaae0 100644
--- a/tests/lib/rules/no-bare-strings-in-template.js
+++ b/tests/lib/rules/no-bare-strings-in-template.js
@@ -132,6 +132,32 @@ tester.run('no-bare-strings-in-template', rule, {
`,
options: [{ allowlist: ['@@'] }]
+ },
+ // regex
+ {
+ code: `
+
+ 123 321
+
+ `,
+ options: [{ allowlist: [String.raw`/\d+/g`] }]
+ },
+ {
+ code: `
+
+ $foo
+ $bar
+
+ `,
+ options: [{ allowlist: [String.raw`/\$\w+/`] }]
+ },
+ {
+ code: `
+
+ foo123foo
+
+ `,
+ options: [{ allowlist: [String.raw`/\d+/`, 'foo'] }]
}
],
invalid: [
@@ -316,6 +342,40 @@ tester.run('no-bare-strings-in-template', rule, {
endColumn: 34
}
]
+ },
+ {
+ code: `
+
+ 123, foo is invalid, 321
+
+ `,
+ options: [{ allowlist: [String.raw`/^\d+$/g`] }],
+ errors: [
+ {
+ messageId: 'unexpected',
+ line: 3,
+ column: 13,
+ endLine: 3,
+ endColumn: 37
+ }
+ ]
+ },
+ {
+ code: `
+
+ foo123bar
+
+ `,
+ options: [{ allowlist: [String.raw`/\d+/`, 'foo'] }],
+ errors: [
+ {
+ messageId: 'unexpected',
+ line: 3,
+ column: 13,
+ endLine: 3,
+ endColumn: 22
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js
index 124442ec2..2df95908c 100644
--- a/tests/lib/rules/no-dupe-keys.js
+++ b/tests/lib/rules/no-dupe-keys.js
@@ -466,7 +466,7 @@ ruleTester.run('no-dupe-keys', rule, {
{
filename: 'test.vue',
code: `
-
+
`,
@@ -475,7 +475,7 @@ ruleTester.run('no-dupe-keys', rule, {
{
filename: 'test.vue',
code: `
-
+
`,
@@ -500,6 +500,17 @@ ruleTester.run('no-dupe-keys', rule, {
parser: require('vue-eslint-parser'),
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
}
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: { parser: require('vue-eslint-parser') }
}
],
@@ -1105,6 +1116,24 @@ ruleTester.run('no-dupe-keys', rule, {
line: 5
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: { parser: require('vue-eslint-parser') },
+ errors: [
+ {
+ message:
+ "Duplicate key 'bar'. May cause name collision in script or template tag.",
+ line: 5
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/no-export-in-script-setup.js b/tests/lib/rules/no-export-in-script-setup.js
index 8703dc74a..bf24b8682 100644
--- a/tests/lib/rules/no-export-in-script-setup.js
+++ b/tests/lib/rules/no-export-in-script-setup.js
@@ -92,20 +92,62 @@ ruleTester.run('no-export-in-script-setup', rule, {
export * from 'foo'
export default {}
export class A {}
+ export const test = '123'
+ export function foo() {}
+ const a = 1
+ export { a }
+ export { fao } from 'bar'
`,
errors: [
{
message: '`
`,
+ `
+
+ `,
+ `
+
+ `,
`
`
+ },
+ {
+ filename: 'multiple-scripts-setup-first.vue',
+ code: `
+
+
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'multiple-scripts-setup-last.vue',
+ code: `
+
+
+
+
+
+
+
+ `
}
],
invalid: [
@@ -420,6 +454,60 @@ tester.run('prefer-use-template-ref', rule, {
column: 28
}
]
+ },
+ {
+ filename: 'multiple-scripts-setup-first.vue',
+ code: `
+
+
+
+
+
+
+
+ `,
+ errors: [
+ {
+ messageId: 'preferUseTemplateRef',
+ data: {
+ name: 'ref'
+ },
+ line: 8,
+ column: 20
+ }
+ ]
+ },
+ {
+ filename: 'multiple-scripts-setup-last.vue',
+ code: `
+
+
+
+
+
+
+
+ `,
+ errors: [
+ {
+ messageId: 'preferUseTemplateRef',
+ data: {
+ name: 'ref'
+ },
+ line: 12,
+ column: 20
+ }
+ ]
}
]
})
diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js
index e352eddf3..43160dfda 100644
--- a/tests/lib/rules/require-default-prop.js
+++ b/tests/lib/rules/require-default-prop.js
@@ -388,6 +388,31 @@ ruleTester.run('require-default-prop', rule, {
parser: require('vue-eslint-parser'),
...languageOptions
}
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions,
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
}
],
@@ -700,6 +725,26 @@ ruleTester.run('require-default-prop', rule, {
line: 3
}
]
+ },
+ {
+ // https://github.com/vuejs/eslint-plugin-vue/issues/2725
+ filename: 'type-with-props-destructure.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ...languageOptions,
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ message: "Prop 'foo' requires default value to be set.",
+ line: 3
+ }
+ ]
}
]
})