Skip to content

Commit a56c7ec

Browse files
authored
Chore: add generate new rule command (#1645)
1 parent c2c709d commit a56c7ec

File tree

3 files changed

+164
-2
lines changed

3 files changed

+164
-2
lines changed

docs/developer-guide/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ Please include as much detail as possible to help us properly address your issue
1313
In order to add a new rule or a rule change, you should:
1414

1515
- Create issue on GitHub with description of proposed rule
16-
- Generate a new rule using the [official yeoman generator](https://github.com/eslint/generator-eslint)
17-
- Run `npm start`
16+
- Generate a new rule using the `npm run new -- [rule-name]` command
1817
- Write test scenarios & implement logic
1918
- Describe the rule in the generated `docs` file
2019
- Make sure all tests are passing
@@ -38,10 +37,12 @@ After opening [astexplorer.net], select `Vue` as the syntax and `vue-eslint-pars
3837
Since single file components in Vue are not plain JavaScript, we can't use the default parser, and we had to introduce additional one: `vue-eslint-parser`, that generates enhanced AST with nodes that represent specific parts of the template syntax, as well as what's inside the `<script>` tag.
3938

4039
To know more about certain nodes in produced ASTs, go here:
40+
4141
- [ESTree docs](https://github.com/estree/estree)
4242
- [vue-eslint-parser AST docs](https://github.com/vuejs/vue-eslint-parser/blob/master/docs/ast.md)
4343

4444
The `vue-eslint-parser` provides few useful parser services, to help traverse the produced AST and access tokens of the template:
45+
4546
- `context.parserServices.defineTemplateBodyVisitor(visitor, scriptVisitor)`
4647
- `context.parserServices.getTemplateBodyTokenStore()`
4748

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"description": "Official ESLint plugin for Vue.js",
55
"main": "lib/index.js",
66
"scripts": {
7+
"new": "node tools/new-rule.js",
78
"start": "npm run test:base -- --watch --growl",
89
"test:base": "mocha \"tests/lib/**/*.js\" --reporter dot",
910
"test": "nyc npm run test:base -- \"tests/integrations/*.js\" --timeout 60000",

tools/new-rule.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
const path = require('path')
2+
const fs = require('fs')
3+
const cp = require('child_process')
4+
const logger = console
5+
6+
// main
7+
;((ruleId) => {
8+
if (ruleId == null) {
9+
logger.error('Usage: npm run new <RuleID>')
10+
process.exitCode = 1
11+
return
12+
}
13+
if (!/^[\w-]+$/u.test(ruleId)) {
14+
logger.error("Invalid RuleID '%s'.", ruleId)
15+
process.exitCode = 1
16+
return
17+
}
18+
19+
const ruleFile = path.resolve(__dirname, `../lib/rules/${ruleId}.js`)
20+
const testFile = path.resolve(__dirname, `../tests/lib/rules/${ruleId}.js`)
21+
const docFile = path.resolve(__dirname, `../docs/rules/${ruleId}.md`)
22+
23+
fs.writeFileSync(
24+
ruleFile,
25+
`/**
26+
* @author *****your name*****
27+
* See LICENSE file in root directory for full license.
28+
*/
29+
'use strict'
30+
31+
// ------------------------------------------------------------------------------
32+
// Requirements
33+
// ------------------------------------------------------------------------------
34+
35+
// ...
36+
37+
// ------------------------------------------------------------------------------
38+
// Helpers
39+
// ------------------------------------------------------------------------------
40+
41+
// ...
42+
43+
// ------------------------------------------------------------------------------
44+
// Rule Definition
45+
// ------------------------------------------------------------------------------
46+
47+
module.exports = {
48+
meta: {
49+
type: 'problem',
50+
docs: {
51+
description: '',
52+
categories: undefined,
53+
url: ''
54+
},
55+
fixable: null,
56+
schema: [],
57+
messages: {
58+
// ...
59+
}
60+
},
61+
/** @param {RuleContext} context */
62+
create(context) {
63+
// ...
64+
65+
return utils.defineTemplateBodyVisitor(context, {
66+
// ...
67+
})
68+
}
69+
}
70+
`
71+
)
72+
fs.writeFileSync(
73+
testFile,
74+
`/**
75+
* @author *****your name*****
76+
* See LICENSE file in root directory for full license.
77+
*/
78+
'use strict'
79+
80+
const RuleTester = require('eslint').RuleTester
81+
const rule = require('../../../lib/rules/${ruleId}')
82+
83+
const tester = new RuleTester({
84+
parser: require.resolve('vue-eslint-parser'),
85+
parserOptions: {
86+
ecmaVersion: 2020,
87+
sourceType: 'module'
88+
}
89+
})
90+
91+
tester.run('${ruleId}', rule, {
92+
valid: [
93+
{
94+
filename: 'test.vue',
95+
code: \`
96+
<template>
97+
98+
</template>
99+
\`
100+
},
101+
],
102+
invalid: [
103+
{
104+
filename: 'test.vue',
105+
code: \`
106+
<template>
107+
108+
</template>
109+
\`,
110+
errors: [
111+
{
112+
message: '...',
113+
line: 'line',
114+
column: 'col'
115+
},
116+
]
117+
}
118+
]
119+
})
120+
`
121+
)
122+
fs.writeFileSync(
123+
docFile,
124+
`---
125+
pageClass: rule-details
126+
sidebarDepth: 0
127+
title: vue/${ruleId}
128+
description: xxx
129+
---
130+
# vue/${ruleId}
131+
132+
> xxx
133+
134+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
135+
136+
## :book: Rule Details
137+
138+
This rule ....
139+
140+
<eslint-code-block :rules="{'vue/${ruleId}': ['error']}">
141+
142+
\`\`\`vue
143+
<template>
144+
145+
</template>
146+
\`\`\`
147+
148+
</eslint-code-block>
149+
150+
## :wrench: Options
151+
152+
Nothing.
153+
154+
`
155+
)
156+
157+
cp.execSync(`code "${ruleFile}"`)
158+
cp.execSync(`code "${testFile}"`)
159+
cp.execSync(`code "${docFile}"`)
160+
})(process.argv[2])

0 commit comments

Comments
 (0)