Skip to content

Commit 9e734a8

Browse files
authored
Merge pull request #2435 from tf/lint-doc-js-yml
Enforce documentation.yml toc coverage via ESLint
2 parents e3e00dc + 21f076d commit 9e734a8

8 files changed

Lines changed: 155 additions & 3 deletions

File tree

entry_types/scrolled/package/.eslintrc.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ module.exports = {
4848
"patterns": ["**/entryState/**", "../**/entryState"]
4949
}]
5050
}
51+
},
52+
{
53+
// Directories passed to documentation.js in
54+
// .github/workflows/docs.yml.
55+
"files": ["src/**/*.js", "spec/support/**/*.js"],
56+
"rules": {
57+
"documented-in-toc": "error"
58+
}
5159
}
5260
]
5361
};

entry_types/scrolled/package/documentation.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ toc:
55
children:
66
- frontend_contentElementTypes
77
- frontend_widgetTypes
8+
- frontend_contentElementErrorBoundary
89
- name: Editor API
910
description: |
1011
Main entry point of editor API to register new content element types.
@@ -45,6 +46,7 @@ toc:
4546
- useLegalInfo
4647
- useMediaMuted
4748
- usePortraitOrientation
49+
- usePrivacyLink
4850
- useShareProviders
4951
- useShareUrl
5052
- useTheme
@@ -60,6 +62,7 @@ toc:
6062
- normalizeSeed
6163
- renderInEntry
6264
- renderInContentElement
65+
- renderInEntryWithContentElementLifecycle
6366
- renderHookInEntry
6467
- name: Storybook Support
6568
description: |

entry_types/scrolled/package/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
},
8787
"scripts": {
8888
"test": "jest",
89-
"lint": "eslint",
89+
"lint": "eslint --rulesdir ../../../node_modules/pageflow/config/eslint-rules",
9090
"start-storybook": "storybook dev --port 8001",
9191
"build-storybook": "storybook build -o .storybook/out",
9292
"snapshot": "storybook build --quiet -o .storybook/out && PERCY_TOKEN=${PERCY_TOKEN:-$PT} percy storybook .storybook/out"

entry_types/scrolled/package/src/frontend/api/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export const api = {
1212
* typeName (string), configuration (object), fallback (function returning
1313
* default UI), and children (content element).
1414
*
15-
* @property {React.Component} contentElementErrorBoundary
15+
* @name frontend_contentElementErrorBoundary
16+
* @type {React.Component}
1617
*/
1718
contentElementErrorBoundary: undefined
1819
}

package/.eslintrc.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ module.exports = {
4646
{
4747
"files": ["spec/**/*.js", "src/testHelpers/**/*.js"],
4848
"extends": ["plugin:jest/recommended"]
49+
},
50+
{
51+
// Directories passed to documentation.js in
52+
// .github/workflows/docs.yml.
53+
"files": [
54+
"src/editor/**/*.js",
55+
"src/ui/**/*.js",
56+
"src/testHelpers/**/*.js"
57+
],
58+
"rules": {
59+
"documented-in-toc": "error"
60+
}
4961
}
5062
]
5163
};
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const yaml = require('js-yaml');
4+
5+
// JSDoc comments on these are either nested under another item or
6+
// excluded from the generated docs, so they do not need toc entries.
7+
const skippedTags = /@(private|internal|ignore|memberof)\b/;
8+
9+
module.exports = {
10+
meta: {
11+
type: 'suggestion',
12+
docs: {
13+
description:
14+
'Ensure JSDoc documented top level items are listed in the toc ' +
15+
'of documentation.yml. Otherwise they render at an arbitrary ' +
16+
'spot at the end of the generated docs.'
17+
},
18+
schema: []
19+
},
20+
21+
create(context) {
22+
const config = findTocConfig(path.dirname(context.getFilename()));
23+
24+
if (!config) {
25+
return {};
26+
}
27+
28+
const sourceCode = context.getSourceCode();
29+
30+
return {
31+
Program(program) {
32+
program.body.forEach(statement => {
33+
const comment = jsdocCommentBefore(statement);
34+
35+
if (!comment || skippedTags.test(comment.value)) {
36+
return;
37+
}
38+
39+
const name = documentedName(comment, statement);
40+
41+
if (name && !config.names.has(name)) {
42+
context.report({
43+
loc: comment.loc,
44+
message:
45+
`'${name}' has a JSDoc comment, but is not listed in the ` +
46+
`toc in ${config.relativePath}. Add it to the section ` +
47+
'where it should show up in the generated docs.'
48+
});
49+
}
50+
});
51+
}
52+
};
53+
54+
function jsdocCommentBefore(statement) {
55+
const comments = sourceCode.getCommentsBefore(statement);
56+
const comment = comments[comments.length - 1];
57+
58+
return comment &&
59+
comment.type === 'Block' &&
60+
comment.value.startsWith('*') ? comment : null;
61+
}
62+
}
63+
};
64+
65+
function documentedName(comment, statement) {
66+
const tagMatch = comment.value.match(/@(?:name|alias)\s+([\w.#]+)/);
67+
68+
if (tagMatch) {
69+
return tagMatch[1];
70+
}
71+
72+
return declaredName(statement);
73+
}
74+
75+
function declaredName(statement) {
76+
switch (statement.type) {
77+
case 'ExportNamedDeclaration':
78+
case 'ExportDefaultDeclaration':
79+
return statement.declaration && declaredName(statement.declaration);
80+
case 'FunctionDeclaration':
81+
case 'ClassDeclaration':
82+
return statement.id && statement.id.name;
83+
case 'VariableDeclaration':
84+
return statement.declarations[0].id.type === 'Identifier' ?
85+
statement.declarations[0].id.name : null;
86+
default:
87+
return null;
88+
}
89+
}
90+
91+
const configCache = new Map();
92+
93+
function findTocConfig(directory) {
94+
if (configCache.has(directory)) {
95+
return configCache.get(directory);
96+
}
97+
98+
const configPath = path.join(directory, 'documentation.yml');
99+
let config;
100+
101+
if (fs.existsSync(configPath)) {
102+
config = loadTocConfig(configPath);
103+
}
104+
else {
105+
const parent = path.dirname(directory);
106+
config = parent === directory ? null : findTocConfig(parent);
107+
}
108+
109+
configCache.set(directory, config);
110+
return config;
111+
}
112+
113+
function loadTocConfig(configPath) {
114+
const toc = yaml.safeLoad(fs.readFileSync(configPath, 'utf8')).toc || [];
115+
const names = new Set();
116+
117+
toc.forEach(section =>
118+
(section.children || []).forEach(child => names.add(child))
119+
);
120+
121+
return {
122+
names,
123+
relativePath: path.relative(process.cwd(), configPath)
124+
};
125+
}

package/documentation.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ toc:
5050
- EditConfigurationView
5151
- ReferenceInputView
5252
- FileInputView
53+
- OembedUrlInputView
5354

5455
- name: Editor - Misc Views
5556
description: |
5657
General purpose Backbone views and mixins exported by `pageflow/editor`.
5758
children:
5859
- modelLifecycleTrackingView
5960
- DropDownButtonView
61+
- DestroyMenuItem
6062
- ListView
6163
- ModelThumbnailView
6264

@@ -132,4 +134,5 @@ toc:
132134
children:
133135
- factories
134136
- setupGlobals
137+
- useFakeFeatures
135138
- useFakeTranslations

package/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
},
2222
"scripts": {
2323
"test": "jest",
24-
"lint": "eslint"
24+
"lint": "eslint --rulesdir config/eslint-rules"
2525
},
2626
"dependencies": {
2727
"backbone-events-standalone": "^0.2.7",

0 commit comments

Comments
 (0)