Skip to content

Commit 0a047d5

Browse files
committed
feat(plugin-jsdocs): add setup wizard binding
1 parent 3d440fa commit 0a047d5

File tree

5 files changed

+186
-1
lines changed

5 files changed

+186
-1
lines changed

packages/create-cli/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ Each plugin exposes its own configuration keys that can be passed as CLI argumen
7272
| **`--lighthouse.urls`** | `string \| string[]` | `http://localhost:4200` | Target URL(s) (comma-separated) |
7373
| **`--lighthouse.categories`** | `('performance'` \| `'a11y'` \| `'best-practices'` \| `'seo')[]` | all | Lighthouse categories |
7474

75+
#### JSDocs
76+
77+
| Option | Type | Default | Description |
78+
| ------------------------- | -------------------- | -------------------------------------------- | -------------------------------------- |
79+
| **`--jsdocs.patterns`** | `string \| string[]` | `src/**/*.ts, src/**/*.js, !**/node_modules` | Source file patterns (comma-separated) |
80+
| **`--jsdocs.categories`** | `boolean` | `true` | Add JSDocs categories |
81+
7582
#### Axe
7683

7784
| Option | Type | Default | Description |

packages/create-cli/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { axeSetupBinding } from '@code-pushup/axe-plugin';
55
import { coverageSetupBinding } from '@code-pushup/coverage-plugin';
66
import { eslintSetupBinding } from '@code-pushup/eslint-plugin';
77
import { jsPackagesSetupBinding } from '@code-pushup/js-packages-plugin';
8+
import { jsDocsSetupBinding } from '@code-pushup/jsdocs-plugin';
89
import { lighthouseSetupBinding } from '@code-pushup/lighthouse-plugin';
910
import { typescriptSetupBinding } from '@code-pushup/typescript-plugin';
1011
import { parsePluginSlugs, validatePluginSlugs } from './lib/setup/plugins.js';
@@ -16,14 +17,14 @@ import {
1617
} from './lib/setup/types.js';
1718
import { runSetupWizard } from './lib/setup/wizard.js';
1819

19-
// TODO: create, import and pass remaining plugin bindings (jsdocs)
2020
const bindings: PluginSetupBinding[] = [
2121
eslintSetupBinding,
2222
coverageSetupBinding,
2323
jsPackagesSetupBinding,
2424
typescriptSetupBinding,
2525
lighthouseSetupBinding,
2626
axeSetupBinding,
27+
jsDocsSetupBinding,
2728
];
2829

2930
const argv = await yargs(hideBin(process.argv))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { jsDocsPlugin } from './lib/jsdocs-plugin.js';
22

33
export default jsDocsPlugin;
4+
export { jsDocsSetupBinding } from './lib/binding.js';
45
export type { JsDocsPluginConfig } from './lib/config.js';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createRequire } from 'node:module';
2+
import type {
3+
CategoryConfig,
4+
PluginAnswer,
5+
PluginSetupBinding,
6+
} from '@code-pushup/models';
7+
import {
8+
answerBoolean,
9+
answerNonEmptyArray,
10+
singleQuote,
11+
} from '@code-pushup/utils';
12+
import { PLUGIN_SLUG, PLUGIN_TITLE } from './constants.js';
13+
14+
const { name: PACKAGE_NAME } = createRequire(import.meta.url)(
15+
'../../package.json',
16+
) as typeof import('../../package.json');
17+
18+
const DEFAULT_PATTERNS: [string, ...string[]] = [
19+
'src/**/*.ts',
20+
'src/**/*.js',
21+
'!**/node_modules',
22+
];
23+
24+
const CATEGORY: CategoryConfig = {
25+
slug: 'docs',
26+
title: 'Documentation',
27+
description: 'Measures how much of your code is **documented**.',
28+
refs: [
29+
{
30+
type: 'group',
31+
plugin: PLUGIN_SLUG,
32+
slug: 'documentation-coverage',
33+
weight: 1,
34+
},
35+
],
36+
};
37+
38+
type JsDocsOptions = {
39+
patterns: [string, ...string[]];
40+
categories: boolean;
41+
};
42+
43+
export const jsDocsSetupBinding = {
44+
slug: PLUGIN_SLUG,
45+
title: PLUGIN_TITLE,
46+
packageName: PACKAGE_NAME,
47+
prompts: async () => [
48+
{
49+
key: 'jsdocs.patterns',
50+
message: 'Source file patterns (comma-separated)',
51+
type: 'input',
52+
default: DEFAULT_PATTERNS.join(', '),
53+
},
54+
{
55+
key: 'jsdocs.categories',
56+
message: 'Add JSDocs categories?',
57+
type: 'confirm',
58+
default: true,
59+
},
60+
],
61+
generateConfig: (answers: Record<string, PluginAnswer>) => {
62+
const options = parseAnswers(answers);
63+
return {
64+
imports: [
65+
{ moduleSpecifier: PACKAGE_NAME, defaultImport: 'jsDocsPlugin' },
66+
],
67+
pluginInit: formatPluginInit(options.patterns),
68+
...(options.categories ? { categories: [CATEGORY] } : {}),
69+
};
70+
},
71+
} satisfies PluginSetupBinding;
72+
73+
function parseAnswers(answers: Record<string, PluginAnswer>): JsDocsOptions {
74+
return {
75+
patterns: answerNonEmptyArray(
76+
answers,
77+
'jsdocs.patterns',
78+
DEFAULT_PATTERNS[0],
79+
),
80+
categories: answerBoolean(answers, 'jsdocs.categories'),
81+
};
82+
}
83+
84+
function formatPluginInit([first, ...rest]: [string, ...string[]]): string[] {
85+
if (rest.length === 0) {
86+
return [`jsDocsPlugin(${singleQuote(first)}),`];
87+
}
88+
return [
89+
'jsDocsPlugin([',
90+
...[first, ...rest].map(p => ` ${singleQuote(p)},`),
91+
']),',
92+
];
93+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { PluginAnswer } from '@code-pushup/models';
2+
import { jsDocsSetupBinding as binding } from './binding.js';
3+
4+
const defaultAnswers: Record<string, PluginAnswer> = {
5+
'jsdocs.patterns': 'src/**/*.ts, src/**/*.js, !**/node_modules',
6+
'jsdocs.categories': true,
7+
};
8+
9+
const noCategoryAnswers: Record<string, PluginAnswer> = {
10+
...defaultAnswers,
11+
'jsdocs.categories': false,
12+
};
13+
14+
describe('jsDocsSetupBinding', () => {
15+
describe('prompts', () => {
16+
it('should default to common TypeScript and JavaScript source patterns', async () => {
17+
await expect(binding.prompts!()).resolves.toIncludeAllPartialMembers([
18+
{
19+
key: 'jsdocs.patterns',
20+
type: 'input',
21+
default: 'src/**/*.ts, src/**/*.js, !**/node_modules',
22+
},
23+
]);
24+
});
25+
26+
it('should offer to add categories by default', async () => {
27+
await expect(binding.prompts!()).resolves.toIncludeAllPartialMembers([
28+
{ key: 'jsdocs.categories', type: 'confirm', default: true },
29+
]);
30+
});
31+
});
32+
33+
describe('generateConfig', () => {
34+
it('should import from @code-pushup/jsdocs-plugin', () => {
35+
const { imports } = binding.generateConfig(defaultAnswers);
36+
expect(imports).toStrictEqual([
37+
expect.objectContaining({
38+
defaultImport: 'jsDocsPlugin',
39+
}),
40+
]);
41+
});
42+
43+
it('should pass multiple patterns as array to plugin call', () => {
44+
const { pluginInit } = binding.generateConfig(defaultAnswers);
45+
expect(pluginInit).toStrictEqual([
46+
'jsDocsPlugin([',
47+
" 'src/**/*.ts',",
48+
" 'src/**/*.js',",
49+
" '!**/node_modules',",
50+
']),',
51+
]);
52+
});
53+
54+
it('should pass single pattern as string to plugin call', () => {
55+
const { pluginInit } = binding.generateConfig({
56+
...defaultAnswers,
57+
'jsdocs.patterns': 'src/**/*.ts',
58+
});
59+
expect(pluginInit).toStrictEqual(["jsDocsPlugin('src/**/*.ts'),"]);
60+
});
61+
62+
it('should generate Documentation category from documentation-coverage group', () => {
63+
const { categories } = binding.generateConfig(defaultAnswers);
64+
expect(categories).toStrictEqual([
65+
expect.objectContaining({
66+
slug: 'docs',
67+
title: 'Documentation',
68+
refs: [
69+
expect.objectContaining({
70+
plugin: 'jsdocs',
71+
slug: 'documentation-coverage',
72+
}),
73+
],
74+
}),
75+
]);
76+
});
77+
78+
it('should omit categories when declined', () => {
79+
const { categories } = binding.generateConfig(noCategoryAnswers);
80+
expect(categories).toBeUndefined();
81+
});
82+
});
83+
});

0 commit comments

Comments
 (0)