Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 095ef4f

Browse files
Chore: add require-meta-docs-url internal rule (#341)
* Chore: move `create` function into object literal I found that `eslint-plugin-eslint-plugin` rules are using `create` function to detect ESLint rule files. If `create` is not a method (includes a variable reference), `eslint-plugin-eslint-plugin` rules don't work properly. * Chore: add require-meta-docs-url rule * Chore: update meta.docs.url * Chore: add version script for meta.docs.url * Fix: apply require-meta-docs-url rule
1 parent 26ab87a commit 095ef4f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1819
-1859
lines changed

‎.eslintrc.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
'use strict'
2+
3+
const version = require('./package.json').version
4+
15
module.exports = {
26
root: true,
37
parserOptions: {
@@ -15,8 +19,19 @@ module.exports = {
1519
'eslint-plugin'
1620
],
1721
rules: {
18-
'eslint-plugin/report-message-format': ['error', '^[A-Z].*\\.$'],
22+
'eslint-plugin/report-message-format': ['error', '^[A-Z`\'].*\\.$'],
1923
'eslint-plugin/prefer-placeholders': 'error',
2024
'eslint-plugin/consistent-output': 'error'
21-
}
25+
},
26+
27+
overrides: [{
28+
files: ['lib/rules/*.js'],
29+
rules: {
30+
"consistent-docs-description": "error",
31+
"no-invalid-meta": "error",
32+
"require-meta-docs-url": ["error", {
33+
"pattern": `https://github.com/vuejs/eslint-plugin-vue/blob/v${version}/docs/rules/{{name}}.md`
34+
}]
35+
}
36+
}]
2237
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
* @author Teddy Katz <https://github.com/not-an-aardvark>
4+
*
5+
* Three functions `isNormalFunctionExpression`, `getKeyName`, and `getRuleInfo`
6+
* are copied from https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/lib/utils.js
7+
*
8+
* I have a plan to send this rule to that plugin: https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/55
9+
*/
10+
11+
'use strict'
12+
13+
// -----------------------------------------------------------------------------
14+
// Requirements
15+
// -----------------------------------------------------------------------------
16+
17+
const path = require('path')
18+
19+
// -----------------------------------------------------------------------------
20+
// Helpers
21+
// -----------------------------------------------------------------------------
22+
23+
/**
24+
* Determines whether a node is a 'normal' (i.e. non-async, non-generator) function expression.
25+
* @param {ASTNode} node The node in question
26+
* @returns {boolean} `true` if the node is a normal function expression
27+
*/
28+
function isNormalFunctionExpression (node) {
29+
return (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.generator && !node.async
30+
}
31+
32+
/**
33+
* Gets the key name of a Property, if it can be determined statically.
34+
* @param {ASTNode} node The `Property` node
35+
* @returns {string|null} The key name, or `null` if the name cannot be determined statically.
36+
*/
37+
function getKeyName (property) {
38+
if (!property.computed && property.key.type === 'Identifier') {
39+
return property.key.name
40+
}
41+
if (property.key.type === 'Literal') {
42+
return '' + property.key.value
43+
}
44+
if (property.key.type === 'TemplateLiteral' && property.key.quasis.length === 1) {
45+
return property.key.quasis[0].value.cooked
46+
}
47+
return null
48+
}
49+
50+
/**
51+
* Performs static analysis on an AST to try to determine the final value of `module.exports`.
52+
* @param {ASTNode} ast The `Program` AST node
53+
* @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes
54+
for the final values of `module.exports.meta` and `module.exports.create`. `isNewStyle` will be `true` if `module.exports`
55+
is an object, and `false` if module.exports is just the `create` function. If no valid ESLint rule info can be extracted
56+
from the file, the return value will be `null`.
57+
*/
58+
function getRuleInfo (ast) {
59+
const INTERESTING_KEYS = new Set(['create', 'meta'])
60+
let exportsVarOverridden = false
61+
let exportsIsFunction = false
62+
63+
const exportNodes = ast.body
64+
.filter(statement => statement.type === 'ExpressionStatement')
65+
.map(statement => statement.expression)
66+
.filter(expression => expression.type === 'AssignmentExpression')
67+
.filter(expression => expression.left.type === 'MemberExpression')
68+
.reduce((currentExports, node) => {
69+
if (
70+
node.left.object.type === 'Identifier' && node.left.object.name === 'module' &&
71+
node.left.property.type === 'Identifier' && node.left.property.name === 'exports'
72+
) {
73+
exportsVarOverridden = true
74+
75+
if (isNormalFunctionExpression(node.right)) {
76+
// Check `module.exports = function () {}`
77+
78+
exportsIsFunction = true
79+
return { create: node.right, meta: null }
80+
} else if (node.right.type === 'ObjectExpression') {
81+
// Check `module.exports = { create: function () {}, meta: {}}`
82+
83+
exportsIsFunction = false
84+
return node.right.properties.reduce((parsedProps, prop) => {
85+
const keyValue = getKeyName(prop)
86+
if (INTERESTING_KEYS.has(keyValue)) {
87+
parsedProps[keyValue] = prop.value
88+
}
89+
return parsedProps
90+
}, {})
91+
}
92+
return {}
93+
} else if (
94+
!exportsIsFunction &&
95+
node.left.object.type === 'MemberExpression' &&
96+
node.left.object.object.type === 'Identifier' && node.left.object.object.name === 'module' &&
97+
node.left.object.property.type === 'Identifier' && node.left.object.property.name === 'exports' &&
98+
node.left.property.type === 'Identifier' && INTERESTING_KEYS.has(node.left.property.name)
99+
) {
100+
// Check `module.exports.create = () => {}`
101+
102+
currentExports[node.left.property.name] = node.right
103+
} else if (
104+
!exportsVarOverridden &&
105+
node.left.object.type === 'Identifier' && node.left.object.name === 'exports' &&
106+
node.left.property.type === 'Identifier' && INTERESTING_KEYS.has(node.left.property.name)
107+
) {
108+
// Check `exports.create = () => {}`
109+
110+
currentExports[node.left.property.name] = node.right
111+
}
112+
return currentExports
113+
}, {})
114+
115+
return Object.prototype.hasOwnProperty.call(exportNodes, 'create') && isNormalFunctionExpression(exportNodes.create)
116+
? Object.assign({ isNewStyle: !exportsIsFunction, meta: null }, exportNodes)
117+
: null
118+
}
119+
120+
// -----------------------------------------------------------------------------
121+
// Rule Definition
122+
// -----------------------------------------------------------------------------
123+
124+
module.exports = {
125+
meta: {
126+
docs: {
127+
description: 'require rules to implement a meta.docs.url property',
128+
category: 'Rules',
129+
recommended: false
130+
},
131+
fixable: 'code',
132+
schema: [{
133+
type: 'object',
134+
properties: {
135+
pattern: { type: 'string' }
136+
},
137+
additionalProperties: false
138+
}]
139+
},
140+
141+
/**
142+
* Creates AST event handlers for require-meta-docs-url.
143+
* @param {RuleContext} context - The rule context.
144+
* @returns {Object} AST event handlers.
145+
*/
146+
create (context) {
147+
const options = context.options[0] || {}
148+
const sourceCode = context.getSourceCode()
149+
const filename = context.getFilename()
150+
const ruleName = filename === '<input>' ? undefined : path.basename(filename, '.js')
151+
const expectedUrl = !options.pattern || !ruleName
152+
? undefined
153+
: options.pattern.replace(/{{\s*name\s*}}/g, ruleName)
154+
155+
/**
156+
* Check whether a given node is the expected URL.
157+
* @param {Node} node The node of property value to check.
158+
* @returns {boolean} `true` if the node is the expected URL.
159+
*/
160+
function isExpectedUrl (node) {
161+
return Boolean(
162+
node &&
163+
node.type === 'Literal' &&
164+
typeof node.value === 'string' &&
165+
(
166+
expectedUrl === undefined ||
167+
node.value === expectedUrl
168+
)
169+
)
170+
}
171+
172+
/**
173+
* Insert a given property into a given object literal.
174+
* @param {SourceCodeFixer} fixer The fixer.
175+
* @param {Node} node The ObjectExpression node to insert a property.
176+
* @param {string} propertyText The property code to insert.
177+
* @returns {void}
178+
*/
179+
function insertProperty (fixer, node, propertyText) {
180+
if (node.properties.length === 0) {
181+
return fixer.replaceText(node, `{\n${propertyText}\n}`)
182+
}
183+
return fixer.insertTextAfter(
184+
sourceCode.getLastToken(node.properties[node.properties.length - 1]),
185+
`,\n${propertyText}`
186+
)
187+
}
188+
189+
return {
190+
Program (node) {
191+
const info = getRuleInfo(node)
192+
if (!info) {
193+
return
194+
}
195+
const metaNode = info.meta
196+
const docsPropNode =
197+
metaNode &&
198+
metaNode.properties &&
199+
metaNode.properties.find(p => p.type === 'Property' && getKeyName(p) === 'docs')
200+
const urlPropNode =
201+
docsPropNode &&
202+
docsPropNode.value.properties &&
203+
docsPropNode.value.properties.find(p => p.type === 'Property' && getKeyName(p) === 'url')
204+
205+
if (isExpectedUrl(urlPropNode && urlPropNode.value)) {
206+
return
207+
}
208+
209+
context.report({
210+
loc:
211+
(urlPropNode && urlPropNode.value.loc) ||
212+
(docsPropNode && docsPropNode.value.loc) ||
213+
(metaNode && metaNode.loc) ||
214+
node.loc.start,
215+
216+
message:
217+
!urlPropNode ? 'Rules should export a `meta.docs.url` property.'
218+
: !expectedUrl ? '`meta.docs.url` property must be a string.'
219+
/* otherwise */ : '`meta.docs.url` property must be `{{expectedUrl}}`.',
220+
221+
data: {
222+
expectedUrl
223+
},
224+
225+
fix (fixer) {
226+
if (expectedUrl) {
227+
const urlString = JSON.stringify(expectedUrl)
228+
if (urlPropNode) {
229+
return fixer.replaceText(urlPropNode.value, urlString)
230+
}
231+
if (docsPropNode && docsPropNode.value.type === 'ObjectExpression') {
232+
return insertProperty(fixer, docsPropNode.value, `url: ${urlString}`)
233+
}
234+
if (!docsPropNode && metaNode && metaNode.type === 'ObjectExpression') {
235+
return insertProperty(fixer, metaNode, `docs: {\nurl: ${urlString}\n}`)
236+
}
237+
}
238+
return null
239+
}
240+
})
241+
}
242+
}
243+
}
244+
}

‎lib/rules/.eslintrc.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

‎lib/rules/attribute-hyphenation.js

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,12 @@ const casing = require('../utils/casing')
1111
// Rule Definition
1212
// ------------------------------------------------------------------------------
1313

14-
function create (context) {
15-
const sourceCode = context.getSourceCode()
16-
const options = context.options[0]
17-
const useHyphenated = options !== 'never'
18-
19-
const caseConverter = casing.getConverter(useHyphenated ? 'kebab-case' : 'camelCase')
20-
21-
function reportIssue (node, name) {
22-
const text = sourceCode.getText(node.key)
23-
24-
context.report({
25-
node: node.key,
26-
loc: node.loc,
27-
message: useHyphenated ? "Attribute '{{text}}' must be hyphenated." : "Attribute '{{text}}' cann't be hyphenated.",
28-
data: {
29-
text
30-
},
31-
fix: fixer => fixer.replaceText(node.key, text.replace(name, caseConverter(name)))
32-
})
33-
}
34-
35-
function isIgnoredAttribute (value) {
36-
if (value.indexOf('data-') !== -1 || value.indexOf('aria-') !== -1) {
37-
return true
38-
}
39-
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
40-
}
41-
42-
// ----------------------------------------------------------------------
43-
// Public
44-
// ----------------------------------------------------------------------
45-
46-
return utils.defineTemplateBodyVisitor(context, {
47-
VAttribute (node) {
48-
if (!utils.isCustomComponent(node.parent.parent)) return
49-
50-
const name = !node.directive ? node.key.rawName : node.key.name === 'bind' ? node.key.raw.argument : false
51-
if (!name || isIgnoredAttribute(name)) return
52-
53-
reportIssue(node, name)
54-
}
55-
})
56-
}
57-
5814
module.exports = {
5915
meta: {
6016
docs: {
6117
description: 'enforce attribute naming style in template',
6218
category: 'strongly-recommended',
63-
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md'
19+
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.0/docs/rules/attribute-hyphenation.md'
6420
},
6521
fixable: 'code',
6622
schema: [
@@ -70,5 +26,47 @@ module.exports = {
7026
]
7127
},
7228

73-
create
29+
create (context) {
30+
const sourceCode = context.getSourceCode()
31+
const options = context.options[0]
32+
const useHyphenated = options !== 'never'
33+
34+
const caseConverter = casing.getConverter(useHyphenated ? 'kebab-case' : 'camelCase')
35+
36+
function reportIssue (node, name) {
37+
const text = sourceCode.getText(node.key)
38+
39+
context.report({
40+
node: node.key,
41+
loc: node.loc,
42+
message: useHyphenated ? "Attribute '{{text}}' must be hyphenated." : "Attribute '{{text}}' cann't be hyphenated.",
43+
data: {
44+
text
45+
},
46+
fix: fixer => fixer.replaceText(node.key, text.replace(name, caseConverter(name)))
47+
})
48+
}
49+
50+
function isIgnoredAttribute (value) {
51+
if (value.indexOf('data-') !== -1 || value.indexOf('aria-') !== -1) {
52+
return true
53+
}
54+
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
55+
}
56+
57+
// ----------------------------------------------------------------------
58+
// Public
59+
// ----------------------------------------------------------------------
60+
61+
return utils.defineTemplateBodyVisitor(context, {
62+
VAttribute (node) {
63+
if (!utils.isCustomComponent(node.parent.parent)) return
64+
65+
const name = !node.directive ? node.key.rawName : node.key.name === 'bind' ? node.key.raw.argument : false
66+
if (!name || isIgnoredAttribute(name)) return
67+
68+
reportIssue(node, name)
69+
}
70+
})
71+
}
7472
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /