Movatterモバイル変換


[0]ホーム

URL:


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

Commit3256f19

Browse files
committed
WIP - documentation validator
1 parent8a0a0dd commit3256f19

File tree

6 files changed

+308
-10
lines changed

6 files changed

+308
-10
lines changed

‎packages/eslint-plugin/docs/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#Writing Rule Documentation
2+
3+
Before you read ahead, know that you don't need to know all of this off the top of your head.
4+
If you run the`docs:check` script, it will apply some validation to your documentation, and help you figure out what order things should be in, and what sections should be included.
5+
6+
Three quick things:
7+
8+
- Every single rule should have a markdown file in the docs/rules folder named`rule-name.md`.
9+
- Every_not deprecated_ rule should have a row within the["Supported Rules"](../../README.md#supported-rules) table in the plugin's`README.md`.
10+
- Every rule based off a`tslint` rule should be appropriately marked up within`ROADMAP.md`.
11+
12+
##Documenting a Rule
13+
14+
A rule's documentation should have the following sections - in this order
15+
Note that sections marked with`(required)` are required, and will block your PR if you skip them.
16+
17+
###Title + Long Description (required)
18+
19+
The document title should be a level 1 heading matching the following pattern:
20+
`# {description from rule.meta.docs.description} ({rule name})`
21+
This should be the one and only level one header.
22+
23+
Immediately proceeding the header must be a long-form description of the rule. There's no hard-and-fast rule about how long this description should be, but you should try to avoid writing more than a few lines unless you think the rule needs the backstory. Your PR reviewer should help you out and ask you to shorten it if they think it's too long.
24+
25+
###Options (required)
26+
27+
This section should begin with a level 2 title matching the following pattern:
28+
`## Options`
29+
30+
If your rule has no options, then you should just include the text`None.`.
31+
If your rule has options, you should do two things:
32+
33+
1. Include a TypeScript code block which uses types/interfaces to describe the options accepted by the rule.
34+
- Everything should have`//` comments briefly describing what each
35+
1. Include a second TypeScript code block which shows the default config for the rule.
36+
37+
###How to Configure (optional)
38+
39+
This section should begin with a level 2 title matching the following pattern:
40+
`## How to Configure`
41+
42+
If your rule is a bit complicated to configure, you should consider adding this section.
43+
If your rule extends a base rule, you must add this section, and in the example you must explicitly show the base rule being disabled.
44+
45+
###Examples (required)
46+
47+
This section should begin with a level 2 title matching the following pattern:
48+
`## Examples`
49+
50+
In this section you should include two TypeScript code blocks; one showing cases your rule will report on, the other showing how to correct those same cases. These examples should be in the form of a TypeScript code block; you can include as many cases within each code block.
51+
52+
If your rule has no options, then you just need a one valid and one invalid block to demonstrate how the rule works.
53+
54+
If your rule has options, you should include one of each block for each option in your rule, demonstrating the effect of each option.

‎packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Grouping overloaded members together can improve readability of the code.
44

5+
##Options
6+
7+
None.
8+
59
##Rule Details
610

711
This rule aims to standardise the way overloaded members are organized.

‎packages/eslint-plugin/tools/validate-docs/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,34 @@ import { checkForRuleDocs } from './check-for-rule-docs';
44
import{parseReadme}from'./parse-readme';
55
import{validateTableStructure}from'./validate-table-structure';
66
import{validateTableRules}from'./validate-table-rules';
7+
import{validateRuleDocs}from'./validate-rule-docs';
78

89
const{ rules}=plugin;
910

1011
lethasErrors=false;
11-
console.log(chalk.underline('Checking for rule docs'));
12-
hasErrors=hasErrors||checkForRuleDocs(rules);
1312

1413
console.log();
15-
console.log(chalk.underline('Valdiating README.md'));
14+
console.log(chalk.underline('Checking rule docs'));
15+
16+
console.log();
17+
console.log(chalk.italic('Checking for existance'));
18+
hasErrors=checkForRuleDocs(rules)||hasErrors;
19+
20+
console.log();
21+
console.log(chalk.italic('Checking content'));
22+
hasErrors=validateRuleDocs(rules)||hasErrors;
23+
24+
console.log();
25+
console.log(chalk.underline('Validating README.md'));
1626
construlesTable=parseReadme();
1727

1828
console.log();
1929
console.log(chalk.italic('Checking table structure...'));
20-
hasErrors=hasErrors||validateTableStructure(rules,rulesTable);
30+
hasErrors=validateTableStructure(rules,rulesTable)||hasErrors;
2131

2232
console.log();
2333
console.log(chalk.italic('Checking rules...'));
24-
hasErrors=hasErrors||validateTableRules(rules,rulesTable);
34+
hasErrors=validateTableRules(rules,rulesTable)||hasErrors;
2535

2636
if(hasErrors){
2737
console.log('\n\n');
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
importchalkfrom'chalk';
22

3+
constTICK=chalk.bold.green('✔');
4+
constCROSS=chalk.bold.red('✗');
5+
6+
constSTARTS_WITH_STRING=/^([]+)/;
37
functionlogRule(
48
success:boolean,
59
ruleName:string,
610
...messages:string[]
711
):void{
812
if(success){
9-
console.log(chalk.bold.green('✔'),chalk.dim(ruleName));
13+
console.log(TICK,chalk.dim(ruleName));
1014
}else{
1115
logError(chalk.bold(ruleName));
12-
messages.forEach(m=>console.error(chalk.bold.red(' -'),m));
16+
messages.forEach(m=>{
17+
constmessagePreIndent=STARTS_WITH_STRING.exec(m);
18+
constindent=messagePreIndent ?`${messagePreIndent[1]}` :' ';
19+
console.error(chalk.bold.red(`${indent}-`),m.trimLeft());
20+
});
1321
}
1422
}
1523

1624
functionlogError(...messages:string[]):void{
17-
console.error(chalk.bold.red('✗'), ...messages);
25+
console.error(CROSS, ...messages);
1826
}
1927

20-
export{logError,logRule};
28+
export{logError,logRule,TICK,CROSS};
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import{TSESLint}from'@typescript-eslint/experimental-utils';
2+
importfsfrom'fs';
3+
importpathfrom'path';
4+
importmarkedfrom'marked';
5+
import{logError,logRule}from'./log';
6+
7+
constdocsFolder=path.resolve(__dirname,'../../docs/rules');
8+
constREQUIRED_TITLE=1;
9+
constOPTIONAL_TITLE=1<<1;
10+
// defines all of the required sections and their expected ordering in the document
11+
leti=1;
12+
enumTitleType{
13+
RuleTitle=(i++<<2)|REQUIRED_TITLE,
14+
Options=(i++<<2)|REQUIRED_TITLE,
15+
HowToConfigure=(i++<<2)|OPTIONAL_TITLE,
16+
Examples=(i++<<2)|REQUIRED_TITLE,
17+
WhenNotToUseIt=(i++<<2)|REQUIRED_TITLE,
18+
RelatedTo=(i++<<2)|OPTIONAL_TITLE,
19+
}
20+
exportfunctiongetTitleTypeValue(key:string):number{
21+
return(TitleType[keyasany]asany)asnumber;
22+
}
23+
24+
constexpectedTitleOrder:TitleType[]=Object.keys(TitleType)
25+
.filter(k=>typeofTitleType[kasany]==='string')
26+
.map(k=>parseInt(k));
27+
28+
typeRule=TSESLint.RuleModule<any,any>&{name:string};
29+
30+
functionvalidateRuleDoc(rule:Rule,ruleDoc:marked.TokensList):boolean{
31+
lethasErrors=false;
32+
consttitleOrder:TitleType[]=[];
33+
constsections:Map<TitleType,marked.Token[]>=newMap();
34+
letlastSeenTitle:TitleType;
35+
36+
functionassertDepth(token:marked.Tokens.Heading,depth:number):void{
37+
if(token.depth!==depth){
38+
hasErrors=true;
39+
logError(
40+
`Expected${
41+
token.text
42+
} to have heading level${depth}, but instead found${token.depth}.`,
43+
);
44+
}
45+
}
46+
functionassertOnlyOne(type:TitleType,name:string):boolean{
47+
if(sections.has(type)){
48+
hasErrors=true;
49+
logError(
50+
`Detected multiple${name} headings when there should be only one.`,
51+
);
52+
53+
returnfalse;
54+
}
55+
56+
returntrue;
57+
}
58+
59+
ruleDoc.forEach(token=>{
60+
if(token.type==='heading'){
61+
if(token.depth===1){
62+
// assume it's the rule title
63+
if(!assertOnlyOne(TitleType.RuleTitle,'level 1')){
64+
return;
65+
}
66+
67+
titleOrder.push(TitleType.RuleTitle);
68+
lastSeenTitle=TitleType.RuleTitle;
69+
sections.set(TitleType.RuleTitle,[]);
70+
71+
constexpectedText=`${rule.meta.docs.description} (${rule.name})`;
72+
if(token.text!==expectedText){
73+
hasErrors=true;
74+
logError(
75+
'Invalid rule title content found.',
76+
`- expected: "${expectedText}"`,
77+
`- received: "${token.text}"`,
78+
);
79+
}
80+
return;
81+
}
82+
83+
if(token.text==='Options'){
84+
if(!assertOnlyOne(TitleType.Options,token.text)){
85+
return;
86+
}
87+
88+
titleOrder.push(TitleType.Options);
89+
lastSeenTitle=TitleType.Options;
90+
sections.set(TitleType.Options,[]);
91+
92+
assertDepth(token,2);
93+
return;
94+
}
95+
96+
if(token.text==='How to Configure'){
97+
if(!assertOnlyOne(TitleType.HowToConfigure,token.text)){
98+
return;
99+
}
100+
101+
titleOrder.push(TitleType.HowToConfigure);
102+
lastSeenTitle=TitleType.HowToConfigure;
103+
sections.set(TitleType.HowToConfigure,[]);
104+
105+
assertDepth(token,2);
106+
return;
107+
}
108+
109+
if(token.text==='Examples'){
110+
if(!assertOnlyOne(TitleType.Examples,token.text)){
111+
return;
112+
}
113+
114+
titleOrder.push(TitleType.Examples);
115+
lastSeenTitle=TitleType.Examples;
116+
sections.set(TitleType.Examples,[]);
117+
118+
assertDepth(token,2);
119+
return;
120+
}
121+
122+
if(token.text==='When Not To Use It'){
123+
if(!assertOnlyOne(TitleType.WhenNotToUseIt,token.text)){
124+
return;
125+
}
126+
127+
titleOrder.push(TitleType.WhenNotToUseIt);
128+
lastSeenTitle=TitleType.WhenNotToUseIt;
129+
sections.set(TitleType.WhenNotToUseIt,[]);
130+
131+
assertDepth(token,2);
132+
return;
133+
}
134+
135+
if(token.text==='RelatedTo'){
136+
if(!assertOnlyOne(TitleType.RelatedTo,token.text)){
137+
return;
138+
}
139+
140+
titleOrder.push(TitleType.RelatedTo);
141+
lastSeenTitle=TitleType.RelatedTo;
142+
sections.set(TitleType.RelatedTo,[]);
143+
144+
assertDepth(token,2);
145+
return;
146+
}
147+
148+
// block other level 2 headers
149+
if(token.depth===2){
150+
hasErrors=true;
151+
logRule(false,rule.name,`Unexpected level 2 header:${token.text}`);
152+
return;
153+
}
154+
}
155+
156+
sections.get(lastSeenTitle)!.push(token);
157+
});
158+
159+
// expect the section order is correct
160+
constsortedTitles=[...titleOrder].sort((a,b)=>a-b);
161+
constisIncorrectlySorted=sortedTitles.some(
162+
(title,i)=>titleOrder[i]!==title,
163+
);
164+
if(isIncorrectlySorted){
165+
hasErrors=true;
166+
logRule(
167+
false,
168+
rule.name,
169+
'Sections are in the wrong order.',
170+
` Expected${sortedTitles.map(t=>TitleType[t]).join(', ')}.`,
171+
` Received${titleOrder.map(t=>TitleType[t]).join(', ')}.`,
172+
);
173+
}
174+
175+
expectedTitleOrder
176+
.filter(t=>(t&OPTIONAL_TITLE)===0)
177+
.forEach(title=>{
178+
if(!titleOrder.includes(title)){
179+
hasErrors=true;
180+
logRule(false,rule.name,`Missing title${TitleType[title]}`);
181+
}
182+
});
183+
184+
if(!hasErrors){
185+
logRule(true,rule.name);
186+
}
187+
188+
returnhasErrors;
189+
}
190+
191+
functionvalidateRuleDocs(
192+
rules:Record<string,TSESLint.RuleModule<any,any>>,
193+
):boolean{
194+
lethasErrors=false;
195+
Object.entries(rules).forEach(([ruleName,rule])=>{
196+
try{
197+
constfileContents=fs.readFileSync(
198+
path.resolve(docsFolder,`${ruleName}.md`),
199+
'utf8',
200+
);
201+
constparsed=marked.lexer(fileContents,{
202+
gfm:true,
203+
tables:true,
204+
silent:false,
205+
});
206+
207+
hasErrors=
208+
validateRuleDoc({name:ruleName, ...rule},parsed)||hasErrors;
209+
}catch(e){
210+
hasErrors=true;
211+
console.error(`Error occurred whilst reading docs for${ruleName}:`,e);
212+
}
213+
});
214+
215+
returnhasErrors;
216+
}
217+
218+
export{validateRuleDocs};

‎packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import{TSESLint}from'@typescript-eslint/experimental-utils';
22
importchalkfrom'chalk';
33
importmarkedfrom'marked';
4-
import{logError}from'./log';
4+
import{logError,TICK}from'./log';
55

66
functionvalidateTableStructure(
77
rules:Record<string,TSESLint.RuleModule<any,any>>,
@@ -42,6 +42,10 @@ function validateTableStructure(
4242
}
4343
});
4444

45+
if(!hasErrors){
46+
console.log(TICK);
47+
}
48+
4549
returnhasErrors;
4650
}
4751

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp