Skip to content
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

Add defaultOptions to all rules #2665

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
Expand Up @@ -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')
}
Expand All @@ -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',

Expand Down Expand Up @@ -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'
}
},
Expand All @@ -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'
}
},
Expand Down
7 changes: 7 additions & 0 deletions lib/rules/attribute-hyphenation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
18 changes: 18 additions & 0 deletions lib/rules/attributes-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}}".`
}
Expand Down
7 changes: 7 additions & 0 deletions lib/rules/block-lang.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}}>'.",
Expand Down
5 changes: 5 additions & 0 deletions lib/rules/block-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}}."
Expand Down
8 changes: 8 additions & 0 deletions lib/rules/block-tag-newline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}}>'.",
Expand Down
1 change: 1 addition & 0 deletions lib/rules/comment-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ reportUnusedDisableDirectives: false }],
messages: {
disableBlock: '--block {{key}}',
enableBlock: '++block',
Expand Down
1 change: 1 addition & 0 deletions lib/rules/component-api-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
1 change: 1 addition & 0 deletions lib/rules/component-definition-name-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = {
enum: allowedCaseOptions
}
],
defaultOptions: ['PascalCase'],
messages: {
incorrectCase: 'Property name "{{value}}" is not {{caseType}}.'
}
Expand Down
8 changes: 8 additions & 0 deletions lib/rules/component-name-in-template-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [
defaultCase,
{
globals: [],
ignores: [],
registeredComponentsOnly: true
}
],
messages: {
incorrectCase: 'Component name "{{name}}" is not {{caseType}}.'
}
Expand Down
1 change: 1 addition & 0 deletions lib/rules/component-options-name-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}}.'
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/custom-event-name-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

/**
Expand Down Expand Up @@ -81,7 +80,7 @@ module.exports = {
type: 'array',
items: [
{
enum: ALLOWED_CASE_OPTIONS
enum: ['kebab-case', 'camelCase']
},
OBJECT_OPTION_SCHEMA
]
Expand All @@ -93,6 +92,7 @@ module.exports = {
}
]
},
defaultOptions: ['camelCase', { ignores: [] }],
messages: {
unexpected: "Custom event name '{{name}}' must be {{caseType}}."
}
Expand Down
1 change: 1 addition & 0 deletions lib/rules/define-emits-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
1 change: 1 addition & 0 deletions lib/rules/define-macros-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).',
Expand Down
1 change: 1 addition & 0 deletions lib/rules/define-props-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand Down
1 change: 1 addition & 0 deletions lib/rules/enforce-style-attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ module.exports = {
additionalProperties: false
}
],
defaultOptions: [{ allow: ['scoped'] }],
messages: {
notAllowedScoped:
'The scoped attribute is not allowed. Allowed: {{ allowedAttrsString }}.',
Expand Down
1 change: 1 addition & 0 deletions lib/rules/first-attribute-linebreak.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand Down
1 change: 1 addition & 0 deletions lib/rules/html-button-has-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading