```vue
-
+
```
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-restricted-html-elements.js b/lib/rules/no-restricted-html-elements.js
index e906d86f2..ab52abde3 100644
--- a/lib/rules/no-restricted-html-elements.js
+++ b/lib/rules/no-restricted-html-elements.js
@@ -10,7 +10,7 @@ module.exports = {
meta: {
type: 'suggestion',
docs: {
- description: 'disallow specific HTML elements',
+ description: 'disallow specific elements',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-restricted-html-elements.html'
},
@@ -23,7 +23,12 @@ module.exports = {
{
type: 'object',
properties: {
- element: { type: 'string' },
+ element: {
+ oneOf: [
+ { type: 'string' },
+ { type: 'array', items: { type: 'string' } }
+ ]
+ },
message: { type: 'string', minLength: 1 }
},
required: ['element'],
@@ -35,7 +40,7 @@ module.exports = {
minItems: 0
},
messages: {
- forbiddenElement: 'Unexpected use of forbidden HTML element {{name}}.',
+ forbiddenElement: 'Unexpected use of forbidden element {{name}}.',
// eslint-disable-next-line eslint-plugin/report-message-format
customMessage: '{{message}}'
}
@@ -50,14 +55,21 @@ module.exports = {
* @param {VElement} node
*/
VElement(node) {
- if (!utils.isHtmlElementNode(node)) {
+ if (
+ !utils.isHtmlElementNode(node) &&
+ !utils.isSvgElementNode(node) &&
+ !utils.isMathElementNode(node)
+ ) {
return
}
for (const option of context.options) {
- const element = option.element || option
+ const restrictedItem = option.element || option
+ const elementsToRestrict = Array.isArray(restrictedItem)
+ ? restrictedItem
+ : [restrictedItem]
- if (element === node.rawName) {
+ if (elementsToRestrict.includes(node.rawName)) {
context.report({
messageId: option.message ? 'customMessage' : 'forbiddenElement',
data: {
@@ -66,6 +78,8 @@ module.exports = {
},
node: node.startTag
})
+
+ return
}
}
}
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/vue3-export-names.json b/lib/utils/vue3-export-names.json
index 349779da1..395090026 100644
--- a/lib/utils/vue3-export-names.json
+++ b/lib/utils/vue3-export-names.json
@@ -235,6 +235,7 @@
"AsyncComponentOptions",
"defineAsyncComponent",
"useModel",
+ "TemplateRef",
"useTemplateRef",
"useId",
"h",
@@ -263,8 +264,8 @@
"devtools",
"setDevtoolsHook",
"DeprecationTypes",
- "WatchOptionsBase",
"createElementVNode",
+ "WatchOptionsBase",
"TransitionProps",
"Transition",
"TransitionGroupProps",
diff --git a/package.json b/package.json
index 475c24e60..da1053c00 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-vue",
- "version": "10.0.0",
+ "version": "10.2.0",
"description": "Official ESLint plugin for Vue.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -18,12 +18,15 @@
"lint:fix": "eslint . --fix && markdownlint \"**/*.md\" --fix",
"tsc": "tsc",
"preversion": "npm test && git add .",
- "version": "env-cmd -e version npm run update && npm run lint -- --fix && git add .",
+ "version": "npm run generate:version && git add .",
"update": "node ./tools/update.js",
"update-resources": "node ./tools/update-resources.js",
"docs:watch": "vitepress dev docs",
"predocs:build": "npm run update",
- "docs:build": "vitepress build docs"
+ "docs:build": "vitepress build docs",
+ "generate:version": "env-cmd -e version npm run update && npm run lint -- --fix",
+ "changeset:version": "changeset version && npm run generate:version && git add --all",
+ "changeset:publish": "changeset publish"
},
"files": [
"lib"
@@ -66,10 +69,11 @@
"xml-name-validator": "^4.0.0"
},
"devDependencies": {
+ "@changesets/cli": "^2.29.2",
"@ota-meshi/site-kit-eslint-editor-vue": "^0.2.4",
"@stylistic/eslint-plugin": "^2.12.1",
+ "@svitejs/changesets-changelog-github-compact": "^1.2.0",
"@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 +92,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/eqeqeq.js b/tests/lib/rules/eqeqeq.js
index afd458248..8089ebaaa 100644
--- a/tests/lib/rules/eqeqeq.js
+++ b/tests/lib/rules/eqeqeq.js
@@ -3,7 +3,8 @@
*/
'use strict'
-const RuleTester = require('../../eslint-compat').RuleTester
+const semver = require('semver')
+const { RuleTester, ESLint } = require('../../eslint-compat')
const rule = require('../../../lib/rules/eqeqeq')
const tester = new RuleTester({
@@ -24,7 +25,19 @@ tester.run('eqeqeq', rule, {
invalid: [
{
code: '
',
- errors: ["Expected '===' and instead saw '=='."]
+ errors: [
+ {
+ message: "Expected '===' and instead saw '=='.",
+ suggestions: semver.gte(ESLint.version, '9.26.0')
+ ? [
+ {
+ desc: "Use '===' instead of '=='.",
+ output: `
`
+ }
+ ]
+ : null
+ }
+ ]
},
// CSS vars injection
{
@@ -34,7 +47,24 @@ tester.run('eqeqeq', rule, {
color: v-bind(a == 1 ? 'red' : 'blue')
}
`,
- errors: ["Expected '===' and instead saw '=='."]
+ errors: [
+ {
+ message: "Expected '===' and instead saw '=='.",
+ suggestions: semver.gte(ESLint.version, '9.26.0')
+ ? [
+ {
+ desc: "Use '===' instead of '=='.",
+ output: `
+ `
+ }
+ ]
+ : null
+ }
+ ]
}
]
})
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
+ }
+ ]
}
]
})
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy