Skip to content

Add defaultOptions to all rules #2665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions eslint-internal-rules/no-invalid-meta-default-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* @fileoverview Internal rule to enforce valid default options.
* @author Flo Edelmann
*/

'use strict'

const Ajv = require('ajv')
const metaSchema = require('ajv/lib/refs/json-schema-draft-04.json')

// from https://github.com/eslint/eslint/blob/main/lib/shared/ajv.js
const ajv = new Ajv({
meta: false,
useDefaults: true,
validateSchema: false,
missingRefs: 'ignore',
verbose: true,
schemaId: 'auto'
})
ajv.addMetaSchema(metaSchema)
ajv._opts.defaultMeta = metaSchema.id

// from https://github.com/eslint/eslint/blob/main/lib/config/flat-config-helpers.js
const noOptionsSchema = Object.freeze({
type: 'array',
minItems: 0,
maxItems: 0
})
function getRuleOptionsSchema(schema) {
if (schema === false || typeof schema !== 'object' || schema === null) {
return null
}

if (!Array.isArray(schema)) {
return schema
}

if (schema.length === 0) {
return { ...noOptionsSchema }
}

return {
type: 'array',
items: schema,
minItems: 0,
maxItems: schema.length
}
}

/**
* @param {RuleContext} context
* @param {ASTNode} node
* @returns {any}
*/
function getNodeValue(context, node) {
try {
// eslint-disable-next-line no-eval
return eval(context.getSourceCode().getText(node))
} catch (error) {
return undefined
}
}

/**
* Gets the property of the Object node passed in that has the name specified.
*
* @param {string} propertyName Name of the property to return.
* @param {ASTNode} node The ObjectExpression node.
* @returns {ASTNode} The Property node or null if not found.
*/
function getPropertyFromObject(propertyName, node) {
if (node && node.type === 'ObjectExpression') {
for (const property of node.properties) {
if (property.type === 'Property' && property.key.name === propertyName) {
return property
}
}
}
return null
}

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce correct use of `meta` property in core rules',
categories: ['Internal']
},
schema: [],
messages: {
defaultOptionsNotMatchingSchema:
'Default options do not match the schema.'
}
},

create(context) {
/** @type {ASTNode} */
let exportsNode

return {
AssignmentExpression(node) {
if (
node.left &&
node.right &&
node.left.type === 'MemberExpression' &&
node.left.object.name === 'module' &&
node.left.property.name === 'exports'
) {
exportsNode = node.right
}
},

'Program:exit'() {
const metaProperty = getPropertyFromObject('meta', exportsNode)
if (!metaProperty) {
return
}

const metaSchema = getPropertyFromObject('schema', metaProperty.value)
const metaDefaultOptions = getPropertyFromObject(
'defaultOptions',
metaProperty.value
)

if (
!metaSchema ||
!metaDefaultOptions ||
metaDefaultOptions.value.type !== 'ArrayExpression'
) {
return
}

const defaultOptions = getNodeValue(context, metaDefaultOptions.value)
const schema = getNodeValue(context, metaSchema.value)

if (!defaultOptions || !schema) {
return
}

let validate
try {
validate = ajv.compile(getRuleOptionsSchema(schema))
} catch (error) {
return
}

if (!validate(defaultOptions)) {
context.report({
node: metaDefaultOptions.value,
messageId: 'defaultOptionsNotMatchingSchema'
})
}
}
}
}
}
4 changes: 3 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ module.exports = [
internal: {
rules: {
'no-invalid-meta': require('./eslint-internal-rules/no-invalid-meta'),
'no-invalid-meta-default-options': require('./eslint-internal-rules/no-invalid-meta-default-options'),
'no-invalid-meta-docs-categories': require('./eslint-internal-rules/no-invalid-meta-docs-categories'),
'require-eslint-community': require('./eslint-internal-rules/require-eslint-community')
}
@@ -45,7 +46,6 @@ module.exports = [
// turn off some rules from shared configs in all files
{
rules: {
'eslint-plugin/require-meta-default-options': 'off', // TODO: enable when all rules have defaultOptions
'eslint-plugin/require-meta-docs-recommended': 'off', // use `categories` instead
'eslint-plugin/require-meta-schema-description': 'off',

@@ -225,6 +225,7 @@ module.exports = [
{ pattern: 'https://eslint.vuejs.org/rules/{{name}}.html' }
],
'internal/no-invalid-meta': 'error',
'internal/no-invalid-meta-default-options': 'error',
'internal/no-invalid-meta-docs-categories': 'error'
}
},
@@ -233,6 +234,7 @@ module.exports = [
rules: {
'eslint-plugin/require-meta-docs-url': 'off',
'internal/no-invalid-meta': 'error',
'internal/no-invalid-meta-default-options': 'error',
'internal/no-invalid-meta-docs-categories': 'error'
}
},
7 changes: 7 additions & 0 deletions lib/rules/attribute-hyphenation.js
Original file line number Diff line number Diff line change
@@ -68,6 +68,13 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
'always',
{
ignore: [],
ignoreTags: []
}
],
messages: {
mustBeHyphenated: "Attribute '{{text}}' must be hyphenated.",
cannotBeHyphenated: "Attribute '{{text}}' can't be hyphenated."
18 changes: 18 additions & 0 deletions lib/rules/attributes-order.js
Original file line number Diff line number Diff line change
@@ -455,6 +455,24 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
{
order: [
ATTRS.DEFINITION,
ATTRS.LIST_RENDERING,
ATTRS.CONDITIONALS,
ATTRS.RENDER_MODIFIERS,
ATTRS.GLOBAL,
[ATTRS.UNIQUE, ATTRS.SLOT],
ATTRS.TWO_WAY_BINDING,
ATTRS.OTHER_DIRECTIVES,
[ATTRS.ATTR_DYNAMIC, ATTRS.ATTR_STATIC, ATTRS.ATTR_SHORTHAND_BOOL],
ATTRS.EVENTS,
ATTRS.CONTENT
],
alphabetical: false
}
],
messages: {
expectedOrder: `Attribute "{{currentNode}}" should go before "{{prevNode}}".`
}
7 changes: 7 additions & 0 deletions lib/rules/block-lang.js
Original file line number Diff line number Diff line change
@@ -151,6 +151,13 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
{
script: { allowNoLang: true },
template: { allowNoLang: true },
style: { allowNoLang: true }
}
],
messages: {
expected:
"Only {{allows}} can be used for the 'lang' attribute of '<{{tag}}>'.",
5 changes: 5 additions & 0 deletions lib/rules/block-order.js
Original file line number Diff line number Diff line change
@@ -61,6 +61,11 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
{
order: [['script', 'template'], 'style']
}
],
messages: {
unexpected:
"'<{{elementName}}{{elementAttributes}}>' should be above '<{{firstUnorderedName}}{{firstUnorderedAttributes}}>' on line {{line}}."
8 changes: 8 additions & 0 deletions lib/rules/block-tag-newline.js
Original file line number Diff line number Diff line change
@@ -71,6 +71,14 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
{
singleline: 'consistent',
multiline: 'always',
maxEmptyLines: 0,
blocks: {}
}
],
messages: {
unexpectedOpeningLinebreak:
"There should be no line break after '<{{tag}}>'.",
1 change: 1 addition & 0 deletions lib/rules/comment-directive.js
Original file line number Diff line number Diff line change
@@ -289,6 +289,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ reportUnusedDisableDirectives: false }],
messages: {
disableBlock: '--block {{key}}',
enableBlock: '++block',
1 change: 1 addition & 0 deletions lib/rules/component-api-style.js
Original file line number Diff line number Diff line change
@@ -216,6 +216,7 @@ module.exports = {
minItems: 1
}
],
defaultOptions: [['script-setup', 'composition']],
messages: {
disallowScriptSetup:
'`<script setup>` is not allowed in your project. Use {{allowedApis}} instead.',
1 change: 1 addition & 0 deletions lib/rules/component-definition-name-casing.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ module.exports = {
enum: allowedCaseOptions
}
],
defaultOptions: ['PascalCase'],
messages: {
incorrectCase: 'Property name "{{value}}" is not {{caseType}}.'
}
8 changes: 8 additions & 0 deletions lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
@@ -70,6 +70,14 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
defaultCase,
{
globals: [],
ignores: [],
registeredComponentsOnly: true
}
],
messages: {
incorrectCase: 'Component name "{{name}}" is not {{caseType}}.'
}
1 change: 1 addition & 0 deletions lib/rules/component-options-name-casing.js
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ module.exports = {
fixable: 'code',
hasSuggestions: true,
schema: [{ enum: casing.allowedCaseOptions }],
defaultOptions: ['PascalCase'],
messages: {
caseNotMatched: 'Component name "{{component}}" is not {{caseType}}.',
possibleRenaming: 'Rename component name to be in {{caseType}}.'
4 changes: 2 additions & 2 deletions lib/rules/custom-event-name-casing.js
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@ const { toRegExp } = require('../utils/regexp')
* @typedef {import('../utils').VueObjectData} VueObjectData
*/

const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
const DEFAULT_CASE = 'camelCase'

/**
@@ -81,7 +80,7 @@ module.exports = {
type: 'array',
items: [
{
enum: ALLOWED_CASE_OPTIONS
enum: ['kebab-case', 'camelCase']
},
OBJECT_OPTION_SCHEMA
]
@@ -93,6 +92,7 @@ module.exports = {
}
]
},
defaultOptions: ['camelCase', { ignores: [] }],
messages: {
unexpected: "Custom event name '{{name}}' must be {{caseType}}."
}
1 change: 1 addition & 0 deletions lib/rules/define-emits-declaration.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ module.exports = {
enum: ['type-based', 'type-literal', 'runtime']
}
],
defaultOptions: ['type-based'],
messages: {
hasArg: 'Use type based declaration instead of runtime declaration.',
hasTypeArg: 'Use runtime declaration instead of type based declaration.',
1 change: 1 addition & 0 deletions lib/rules/define-macros-order.js
Original file line number Diff line number Diff line change
@@ -382,6 +382,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ order: DEFAULT_ORDER, defineExposeLast: false }],
messages: {
macrosNotOnTop:
'{{macro}} should be the first statement in `<script setup>` (after any potential import statements or type definitions).',
1 change: 1 addition & 0 deletions lib/rules/define-props-declaration.js
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ module.exports = {
enum: ['type-based', 'runtime']
}
],
defaultOptions: ['type-based'],
messages: {
hasArg: 'Use type-based declaration instead of runtime declaration.',
hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
1 change: 1 addition & 0 deletions lib/rules/enforce-style-attribute.js
Original file line number Diff line number Diff line change
@@ -71,6 +71,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ allow: ['scoped'] }],
messages: {
notAllowedScoped:
'The scoped attribute is not allowed. Allowed: {{ allowedAttrsString }}.',
1 change: 1 addition & 0 deletions lib/rules/first-attribute-linebreak.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ multiline: 'below', singleline: 'ignore' }],
messages: {
expected: 'Expected a linebreak before this attribute.',
unexpected: 'Expected no linebreak before this attribute.'
1 change: 1 addition & 0 deletions lib/rules/html-button-has-type.js
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ button: true, submit: true, reset: true }],
messages: {
missingTypeAttribute: 'Missing an explicit type attribute for button.',
invalidTypeAttribute:
Loading

Unchanged files with check annotations Beta

vue: require('./processor')
},
environments: {
// TODO Remove in the next major version

Check warning on line 287 in lib/index.js

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO Remove in the next major version'
/** @deprecated */
'setup-compiler-macros': {
globals: {
type: 'problem',
docs: {
description: 'enforce valid `defineOptions` compiler macro',
// TODO Switch in the next major version

Check warning on line 15 in lib/rules/valid-define-options.js

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO Switch in the next major version'
// categories: ['vue3-essential', 'vue2-essential'],
categories: undefined,
url: 'https://eslint.vuejs.org/rules/valid-define-options.html'