@api-extractor-tools/eslint-plugin 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +5 -2
- package/CHANGELOG.md +8 -0
- package/dist/rules/index.d.ts +11 -1
- package/dist/rules/package-documentation.d.ts +5 -2
- package/dist/rules/package-documentation.d.ts.map +1 -1
- package/dist/rules/package-documentation.js +41 -10
- package/dist/rules/package-documentation.js.map +1 -1
- package/dist/types.d.ts +20 -6
- package/dist/utils/tsdoc-parser.d.ts +2 -6
- package/package.json +2 -2
- package/src/rules/package-documentation.ts +52 -12
- package/src/types.ts +3 -3
- package/temp/eslint-plugin.api.md +30 -10
- package/test/rules/package-documentation.test.ts +107 -60
package/ARCHITECTURE.md
CHANGED
|
@@ -97,11 +97,14 @@ Requires the TypeScript `override` keyword when the `@override` TSDoc tag is pre
|
|
|
97
97
|
|
|
98
98
|
### package-documentation
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Enforces correct usage of the `@packageDocumentation` tag based on whether a file is a package entry point (barrel file). The rule automatically detects entry points by examining the nearest `package.json`.
|
|
101
|
+
|
|
102
|
+
- **Barrel files** (entry points): Requires `@packageDocumentation` tag to be present.
|
|
103
|
+
- **Non-barrel files**: Reports an error if `@packageDocumentation` tag is found.
|
|
101
104
|
|
|
102
105
|
**Options:** None
|
|
103
106
|
|
|
104
|
-
Note: This rule
|
|
107
|
+
Note: This rule uses `findPackageJson` and `isEntryPoint` from the entry-point utilities to determine barrel file status. If no `package.json` is found, the rule is skipped.
|
|
105
108
|
|
|
106
109
|
## Usage Examples
|
|
107
110
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @api-extractor-tools/eslint-plugin
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#204](https://github.com/mike-north/api-extractor-tools/pull/204) [`3accc97`](https://github.com/mike-north/api-extractor-tools/commit/3accc97733d1b21eb7bbbe82b122a347e9b5ea76) Thanks [@mike-north](https://github.com/mike-north)! - fix: enforce @packageDocumentation only on package barrel files
|
|
8
|
+
|
|
9
|
+
The `package-documentation` rule now automatically detects whether a file is a package entry point by examining the nearest `package.json`. Barrel files are required to have the `@packageDocumentation` tag, and non-barrel files report an error if the tag is present. Previously the rule required the tag on every file regardless of whether it was an entry point.
|
|
10
|
+
|
|
3
11
|
## 0.1.0-alpha.1
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -6,5 +6,15 @@
|
|
|
6
6
|
* All available ESLint rules.
|
|
7
7
|
* @alpha
|
|
8
8
|
*/
|
|
9
|
-
export declare const rules: {
|
|
9
|
+
export declare const rules: {
|
|
10
|
+
readonly 'extra-release-tag': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"extraReleaseTag", [import("./extra-release-tag").ExtraReleaseTagRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
11
|
+
readonly 'forgotten-export': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"forgottenExport", [import("./forgotten-export").ForgottenExportRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
12
|
+
readonly 'incompatible-release-tags': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"incompatibleReleaseTags", [import("./incompatible-release-tags").IncompatibleReleaseTagsRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
13
|
+
readonly 'missing-release-tag': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingReleaseTag", [import("..").MissingReleaseTagRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
14
|
+
readonly 'override-keyword': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingOverrideKeyword", [import("..").OverrideKeywordRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
15
|
+
readonly 'package-documentation': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingPackageDocumentation" | "unexpectedPackageDocumentation", [import("..").PackageDocumentationRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
16
|
+
readonly 'public-on-non-exported': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"publicOnNonExported", [import("./public-on-non-exported").PublicOnNonExportedRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
17
|
+
readonly 'public-on-private-member': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"publicOnPrivateMember", [import("./public-on-private-member").PublicOnPrivateMemberRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
18
|
+
readonly 'valid-enum-type': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingValue" | "invalidValue" | "multipleEnumTypes" | "invalidConstruct" | "missingEnumType", [import("..").ValidEnumTypeRuleOptions], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
19
|
+
};
|
|
10
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ESLint rule requiring @packageDocumentation in files
|
|
2
|
+
* ESLint rule requiring @packageDocumentation in package barrel files
|
|
3
|
+
* and disallowing it in non-barrel files.
|
|
3
4
|
* @internal
|
|
4
5
|
*/
|
|
5
6
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
6
7
|
import type { PackageDocumentationRuleOptions } from '../types';
|
|
7
|
-
|
|
8
|
+
type MessageIds = 'missingPackageDocumentation' | 'unexpectedPackageDocumentation';
|
|
9
|
+
export declare const packageDocumentation: ESLintUtils.RuleModule<MessageIds, [PackageDocumentationRuleOptions], unknown, ESLintUtils.RuleListener>;
|
|
10
|
+
export {};
|
|
8
11
|
//# sourceMappingURL=package-documentation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-documentation.d.ts","sourceRoot":"","sources":["../../src/rules/package-documentation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"package-documentation.d.ts","sourceRoot":"","sources":["../../src/rules/package-documentation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAMtD,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,UAAU,CAAA;AAO/D,KAAK,UAAU,GACX,6BAA6B,GAC7B,gCAAgC,CAAA;AAEpC,eAAO,MAAM,oBAAoB,0GA+E/B,CAAA"}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* ESLint rule requiring @packageDocumentation in files
|
|
3
|
+
* ESLint rule requiring @packageDocumentation in package barrel files
|
|
4
|
+
* and disallowing it in non-barrel files.
|
|
4
5
|
* @internal
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
exports.packageDocumentation = void 0;
|
|
9
|
+
const path = require("path");
|
|
8
10
|
const utils_1 = require("@typescript-eslint/utils");
|
|
9
11
|
const tsdoc_parser_1 = require("../utils/tsdoc-parser");
|
|
12
|
+
const entry_point_1 = require("../utils/entry-point");
|
|
10
13
|
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`);
|
|
11
14
|
exports.packageDocumentation = createRule({
|
|
12
15
|
name: 'package-documentation',
|
|
13
16
|
meta: {
|
|
14
17
|
type: 'suggestion',
|
|
15
18
|
docs: {
|
|
16
|
-
description: 'Require @packageDocumentation tag in files',
|
|
19
|
+
description: 'Require @packageDocumentation tag in package barrel files and disallow it in non-barrel files',
|
|
17
20
|
},
|
|
18
21
|
messages: {
|
|
19
22
|
missingPackageDocumentation: 'File is missing a @packageDocumentation comment. Add a TSDoc comment with @packageDocumentation at the top of the file.',
|
|
23
|
+
unexpectedPackageDocumentation: '@packageDocumentation comment should only be in the package barrel file, not in this file.',
|
|
20
24
|
},
|
|
21
25
|
schema: [
|
|
22
26
|
{
|
|
@@ -29,19 +33,46 @@ exports.packageDocumentation = createRule({
|
|
|
29
33
|
defaultOptions: [{}],
|
|
30
34
|
create(context) {
|
|
31
35
|
const sourceCode = context.sourceCode;
|
|
36
|
+
const filename = context.filename;
|
|
32
37
|
return {
|
|
33
38
|
Program(node) {
|
|
39
|
+
// Find the nearest package.json to determine barrel file status
|
|
40
|
+
const fileDir = path.dirname(path.resolve(filename));
|
|
41
|
+
const pkgPath = (0, entry_point_1.findPackageJson)(fileDir);
|
|
42
|
+
// If no package.json is found, we can't determine barrel file status
|
|
43
|
+
if (!pkgPath) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const isBarrel = (0, entry_point_1.isEntryPoint)(filename, pkgPath);
|
|
34
47
|
const tsdocComments = (0, tsdoc_parser_1.findAllTSDocComments)(sourceCode);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
if (isBarrel) {
|
|
49
|
+
// Barrel files must have @packageDocumentation
|
|
50
|
+
for (const { parsed } of tsdocComments) {
|
|
51
|
+
if (parsed.docComment &&
|
|
52
|
+
(0, tsdoc_parser_1.hasPackageDocumentation)(parsed.docComment)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
context.report({
|
|
57
|
+
node,
|
|
58
|
+
loc: { line: 1, column: 0 },
|
|
59
|
+
messageId: 'missingPackageDocumentation',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Non-barrel files must NOT have @packageDocumentation
|
|
64
|
+
for (const { comment, parsed } of tsdocComments) {
|
|
65
|
+
if (parsed.docComment &&
|
|
66
|
+
(0, tsdoc_parser_1.hasPackageDocumentation)(parsed.docComment)) {
|
|
67
|
+
context.report({
|
|
68
|
+
node,
|
|
69
|
+
loc: comment.loc,
|
|
70
|
+
messageId: 'unexpectedPackageDocumentation',
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
38
74
|
}
|
|
39
75
|
}
|
|
40
|
-
context.report({
|
|
41
|
-
node,
|
|
42
|
-
loc: { line: 1, column: 0 },
|
|
43
|
-
messageId: 'missingPackageDocumentation',
|
|
44
|
-
});
|
|
45
76
|
},
|
|
46
77
|
};
|
|
47
78
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-documentation.js","sourceRoot":"","sources":["../../src/rules/package-documentation.ts"],"names":[],"mappings":";AAAA;;;
|
|
1
|
+
{"version":3,"file":"package-documentation.js","sourceRoot":"","sources":["../../src/rules/package-documentation.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,6BAA4B;AAC5B,oDAAsD;AACtD,wDAG8B;AAC9B,sDAAoE;AAGpE,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,CAAC,IAAI,EAAE,EAAE,CACP,8FAA8F,IAAI,KAAK,CAC1G,CAAA;AAMY,QAAA,oBAAoB,GAAG,UAAU,CAG5C;IACA,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EACT,+FAA+F;SAClG;QACD,QAAQ,EAAE;YACR,2BAA2B,EACzB,yHAAyH;YAC3H,8BAA8B,EAC5B,4FAA4F;SAC/F;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE;gBACd,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,cAAc,EAAE,CAAC,EAAE,CAAC;IACpB,MAAM,CAAC,OAAO;QACZ,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QAEjC,OAAO;YACL,OAAO,CAAC,IAAI;gBACV,gEAAgE;gBAChE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;gBACpD,MAAM,OAAO,GAAG,IAAA,6BAAe,EAAC,OAAO,CAAC,CAAA;gBAExC,qEAAqE;gBACrE,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAM;gBACR,CAAC;gBAED,MAAM,QAAQ,GAAG,IAAA,0BAAY,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;gBAChD,MAAM,aAAa,GAAG,IAAA,mCAAoB,EAAC,UAAU,CAAC,CAAA;gBAEtD,IAAI,QAAQ,EAAE,CAAC;oBACb,+CAA+C;oBAC/C,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;wBACvC,IACE,MAAM,CAAC,UAAU;4BACjB,IAAA,sCAAuB,EAAC,MAAM,CAAC,UAAU,CAAC,EAC1C,CAAC;4BACD,OAAM;wBACR,CAAC;oBACH,CAAC;oBAED,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;wBAC3B,SAAS,EAAE,6BAA6B;qBACzC,CAAC,CAAA;gBACJ,CAAC;qBAAM,CAAC;oBACN,uDAAuD;oBACvD,KAAK,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;wBAChD,IACE,MAAM,CAAC,UAAU;4BACjB,IAAA,sCAAuB,EAAC,MAAM,CAAC,UAAU,CAAC,EAC1C,CAAC;4BACD,OAAO,CAAC,MAAM,CAAC;gCACb,IAAI;gCACJ,GAAG,EAAE,OAAO,CAAC,GAAG;gCAChB,SAAS,EAAE,gCAAgC;6BAC5C,CAAC,CAAA;4BACF,OAAM;wBACR,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAA;IACH,CAAC;CACF,CAAC,CAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -24,9 +24,23 @@ export interface MessageConfig {
|
|
|
24
24
|
* @alpha
|
|
25
25
|
*/
|
|
26
26
|
export interface ApiExtractorMessagesConfig {
|
|
27
|
-
compilerMessageReporting?: {
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
compilerMessageReporting?: {
|
|
28
|
+
[messageId: string]: MessageConfig | undefined;
|
|
29
|
+
default?: MessageConfig;
|
|
30
|
+
};
|
|
31
|
+
extractorMessageReporting?: {
|
|
32
|
+
'ae-extra-release-tag'?: MessageConfig;
|
|
33
|
+
'ae-forgotten-export'?: MessageConfig;
|
|
34
|
+
'ae-incompatible-release-tags'?: MessageConfig;
|
|
35
|
+
'ae-internal-missing-underscore'?: MessageConfig;
|
|
36
|
+
'ae-missing-release-tag'?: MessageConfig;
|
|
37
|
+
[messageId: string]: MessageConfig | undefined;
|
|
38
|
+
default?: MessageConfig;
|
|
39
|
+
};
|
|
40
|
+
tsdocMessageReporting?: {
|
|
41
|
+
[messageId: string]: MessageConfig | undefined;
|
|
42
|
+
default?: MessageConfig;
|
|
43
|
+
};
|
|
30
44
|
}
|
|
31
45
|
/**
|
|
32
46
|
* Partial representation of api-extractor.json relevant for this plugin.
|
|
@@ -80,9 +94,9 @@ export type OverrideKeywordRuleOptions = Record<string, never>;
|
|
|
80
94
|
* Options for the package-documentation rule.
|
|
81
95
|
*
|
|
82
96
|
* @remarks
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
97
|
+
* Automatically detects whether a file is a package entry point by examining
|
|
98
|
+
* the nearest package.json. Requires `@packageDocumentation` on barrel files
|
|
99
|
+
* and reports an error if the tag is found on non-barrel files.
|
|
86
100
|
*
|
|
87
101
|
* @alpha
|
|
88
102
|
*/
|
|
@@ -74,9 +74,7 @@ export declare function extractEnumType(commentText: string): EnumTypeExtraction
|
|
|
74
74
|
* @returns The comment text if a TSDoc comment exists, undefined otherwise
|
|
75
75
|
* @alpha
|
|
76
76
|
*/
|
|
77
|
-
export declare function getLeadingTSDocComment(sourceCode: {
|
|
78
|
-
getCommentsBefore: (node: TSESTree.Node) => TSESTree.Comment[];
|
|
79
|
-
}, node: TSESTree.Node): string | undefined;
|
|
77
|
+
export declare function getLeadingTSDocComment(sourceCode: { getCommentsBefore: (node: TSESTree.Node) => TSESTree.Comment[] }, node: TSESTree.Node): string | undefined;
|
|
80
78
|
/**
|
|
81
79
|
* Finds all TSDoc comments in a source file.
|
|
82
80
|
*
|
|
@@ -84,9 +82,7 @@ export declare function getLeadingTSDocComment(sourceCode: {
|
|
|
84
82
|
* @returns Array of comment objects with their parsed content
|
|
85
83
|
* @alpha
|
|
86
84
|
*/
|
|
87
|
-
export declare function findAllTSDocComments(sourceCode: {
|
|
88
|
-
getAllComments: () => TSESTree.Comment[];
|
|
89
|
-
}): Array<{
|
|
85
|
+
export declare function findAllTSDocComments(sourceCode: { getAllComments: () => TSESTree.Comment[] }): Array<{
|
|
90
86
|
comment: TSESTree.Comment;
|
|
91
87
|
parsed: ParserContext;
|
|
92
88
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@api-extractor-tools/eslint-plugin",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "ESLint plugin providing authoring-time feedback for API Extractor",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/eslint-plugin-public.d.ts",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"tsd": "^0.33.0",
|
|
42
42
|
"typescript": "5.8.3",
|
|
43
43
|
"vitest": "^4.0.15",
|
|
44
|
-
"@api-extractor-tools/declaration-file-normalizer": "0.0
|
|
44
|
+
"@api-extractor-tools/declaration-file-normalizer": "0.1.0-alpha.6"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"clean": "rm -rf dist",
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ESLint rule requiring @packageDocumentation in files
|
|
2
|
+
* ESLint rule requiring @packageDocumentation in package barrel files
|
|
3
|
+
* and disallowing it in non-barrel files.
|
|
3
4
|
* @internal
|
|
4
5
|
*/
|
|
5
6
|
|
|
7
|
+
import * as path from 'path'
|
|
6
8
|
import { ESLintUtils } from '@typescript-eslint/utils'
|
|
7
9
|
import {
|
|
8
10
|
findAllTSDocComments,
|
|
9
11
|
hasPackageDocumentation,
|
|
10
12
|
} from '../utils/tsdoc-parser'
|
|
13
|
+
import { findPackageJson, isEntryPoint } from '../utils/entry-point'
|
|
11
14
|
import type { PackageDocumentationRuleOptions } from '../types'
|
|
12
15
|
|
|
13
16
|
const createRule = ESLintUtils.RuleCreator(
|
|
@@ -15,7 +18,9 @@ const createRule = ESLintUtils.RuleCreator(
|
|
|
15
18
|
`https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`,
|
|
16
19
|
)
|
|
17
20
|
|
|
18
|
-
type MessageIds =
|
|
21
|
+
type MessageIds =
|
|
22
|
+
| 'missingPackageDocumentation'
|
|
23
|
+
| 'unexpectedPackageDocumentation'
|
|
19
24
|
|
|
20
25
|
export const packageDocumentation = createRule<
|
|
21
26
|
[PackageDocumentationRuleOptions],
|
|
@@ -25,11 +30,14 @@ export const packageDocumentation = createRule<
|
|
|
25
30
|
meta: {
|
|
26
31
|
type: 'suggestion',
|
|
27
32
|
docs: {
|
|
28
|
-
description:
|
|
33
|
+
description:
|
|
34
|
+
'Require @packageDocumentation tag in package barrel files and disallow it in non-barrel files',
|
|
29
35
|
},
|
|
30
36
|
messages: {
|
|
31
37
|
missingPackageDocumentation:
|
|
32
38
|
'File is missing a @packageDocumentation comment. Add a TSDoc comment with @packageDocumentation at the top of the file.',
|
|
39
|
+
unexpectedPackageDocumentation:
|
|
40
|
+
'@packageDocumentation comment should only be in the package barrel file, not in this file.',
|
|
33
41
|
},
|
|
34
42
|
schema: [
|
|
35
43
|
{
|
|
@@ -42,22 +50,54 @@ export const packageDocumentation = createRule<
|
|
|
42
50
|
defaultOptions: [{}],
|
|
43
51
|
create(context) {
|
|
44
52
|
const sourceCode = context.sourceCode
|
|
53
|
+
const filename = context.filename
|
|
45
54
|
|
|
46
55
|
return {
|
|
47
56
|
Program(node): void {
|
|
57
|
+
// Find the nearest package.json to determine barrel file status
|
|
58
|
+
const fileDir = path.dirname(path.resolve(filename))
|
|
59
|
+
const pkgPath = findPackageJson(fileDir)
|
|
60
|
+
|
|
61
|
+
// If no package.json is found, we can't determine barrel file status
|
|
62
|
+
if (!pkgPath) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const isBarrel = isEntryPoint(filename, pkgPath)
|
|
48
67
|
const tsdocComments = findAllTSDocComments(sourceCode)
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
if (isBarrel) {
|
|
70
|
+
// Barrel files must have @packageDocumentation
|
|
71
|
+
for (const { parsed } of tsdocComments) {
|
|
72
|
+
if (
|
|
73
|
+
parsed.docComment &&
|
|
74
|
+
hasPackageDocumentation(parsed.docComment)
|
|
75
|
+
) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
53
78
|
}
|
|
54
|
-
}
|
|
55
79
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
80
|
+
context.report({
|
|
81
|
+
node,
|
|
82
|
+
loc: { line: 1, column: 0 },
|
|
83
|
+
messageId: 'missingPackageDocumentation',
|
|
84
|
+
})
|
|
85
|
+
} else {
|
|
86
|
+
// Non-barrel files must NOT have @packageDocumentation
|
|
87
|
+
for (const { comment, parsed } of tsdocComments) {
|
|
88
|
+
if (
|
|
89
|
+
parsed.docComment &&
|
|
90
|
+
hasPackageDocumentation(parsed.docComment)
|
|
91
|
+
) {
|
|
92
|
+
context.report({
|
|
93
|
+
node,
|
|
94
|
+
loc: comment.loc,
|
|
95
|
+
messageId: 'unexpectedPackageDocumentation',
|
|
96
|
+
})
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
61
101
|
},
|
|
62
102
|
}
|
|
63
103
|
},
|
package/src/types.ts
CHANGED
|
@@ -108,9 +108,9 @@ export type OverrideKeywordRuleOptions = Record<string, never>
|
|
|
108
108
|
* Options for the package-documentation rule.
|
|
109
109
|
*
|
|
110
110
|
* @remarks
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* Automatically detects whether a file is a package entry point by examining
|
|
112
|
+
* the nearest package.json. Requires `@packageDocumentation` on barrel files
|
|
113
|
+
* and reports an error if the tag is found on non-barrel files.
|
|
114
114
|
*
|
|
115
115
|
* @alpha
|
|
116
116
|
*/
|
|
@@ -42,11 +42,25 @@ export type ApiExtractorLogLevel = 'error' | 'none' | 'warning';
|
|
|
42
42
|
// @alpha
|
|
43
43
|
export interface ApiExtractorMessagesConfig {
|
|
44
44
|
// (undocumented)
|
|
45
|
-
compilerMessageReporting?: {
|
|
45
|
+
compilerMessageReporting?: {
|
|
46
|
+
[messageId: string]: MessageConfig | undefined;
|
|
47
|
+
default?: MessageConfig;
|
|
48
|
+
};
|
|
46
49
|
// (undocumented)
|
|
47
|
-
extractorMessageReporting?: {
|
|
50
|
+
extractorMessageReporting?: {
|
|
51
|
+
'ae-extra-release-tag'?: MessageConfig;
|
|
52
|
+
'ae-forgotten-export'?: MessageConfig;
|
|
53
|
+
'ae-incompatible-release-tags'?: MessageConfig;
|
|
54
|
+
'ae-internal-missing-underscore'?: MessageConfig;
|
|
55
|
+
'ae-missing-release-tag'?: MessageConfig;
|
|
56
|
+
[messageId: string]: MessageConfig | undefined;
|
|
57
|
+
default?: MessageConfig;
|
|
58
|
+
};
|
|
48
59
|
// (undocumented)
|
|
49
|
-
tsdocMessageReporting?: {
|
|
60
|
+
tsdocMessageReporting?: {
|
|
61
|
+
[messageId: string]: MessageConfig | undefined;
|
|
62
|
+
default?: MessageConfig;
|
|
63
|
+
};
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
// @alpha
|
|
@@ -73,9 +87,7 @@ export interface ExtraReleaseTagRuleOptions {
|
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
// @alpha
|
|
76
|
-
export function findAllTSDocComments(sourceCode: {
|
|
77
|
-
getAllComments: () => TSESTree.Comment[];
|
|
78
|
-
}): Array<{
|
|
90
|
+
export function findAllTSDocComments(sourceCode: { getAllComments: () => TSESTree.Comment[] }): Array<{
|
|
79
91
|
comment: TSESTree.Comment;
|
|
80
92
|
parsed: ParserContext;
|
|
81
93
|
}>;
|
|
@@ -86,9 +98,7 @@ export interface ForgottenExportRuleOptions {
|
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
// @alpha
|
|
89
|
-
export function getLeadingTSDocComment(sourceCode: {
|
|
90
|
-
getCommentsBefore: (node: TSESTree.Node) => TSESTree.Comment[];
|
|
91
|
-
}, node: TSESTree.Node): string | undefined;
|
|
101
|
+
export function getLeadingTSDocComment(sourceCode: { getCommentsBefore: (node: TSESTree.Node) => TSESTree.Comment[] }, node: TSESTree.Node): string | undefined;
|
|
92
102
|
|
|
93
103
|
// @alpha @override
|
|
94
104
|
export function hasOverrideTag(docComment: DocComment): boolean;
|
|
@@ -157,7 +167,17 @@ export interface ResolvedEntryPoints {
|
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
// @alpha
|
|
160
|
-
export const rules: {
|
|
170
|
+
export const rules: {
|
|
171
|
+
readonly 'extra-release-tag': RuleModule<"extraReleaseTag", [ExtraReleaseTagRuleOptions], unknown, RuleListener>;
|
|
172
|
+
readonly 'forgotten-export': RuleModule<"forgottenExport", [ForgottenExportRuleOptions], unknown, RuleListener>;
|
|
173
|
+
readonly 'incompatible-release-tags': RuleModule<"incompatibleReleaseTags", [IncompatibleReleaseTagsRuleOptions], unknown, RuleListener>;
|
|
174
|
+
readonly 'missing-release-tag': RuleModule<"missingReleaseTag", [MissingReleaseTagRuleOptions], unknown, RuleListener>;
|
|
175
|
+
readonly 'override-keyword': RuleModule<"missingOverrideKeyword", [OverrideKeywordRuleOptions], unknown, RuleListener>;
|
|
176
|
+
readonly 'package-documentation': RuleModule<"missingPackageDocumentation" | "unexpectedPackageDocumentation", [PackageDocumentationRuleOptions], unknown, RuleListener>;
|
|
177
|
+
readonly 'public-on-non-exported': RuleModule<"publicOnNonExported", [PublicOnNonExportedRuleOptions], unknown, RuleListener>;
|
|
178
|
+
readonly 'public-on-private-member': RuleModule<"publicOnPrivateMember", [PublicOnPrivateMemberRuleOptions], unknown, RuleListener>;
|
|
179
|
+
readonly 'valid-enum-type': RuleModule<"missingValue" | "invalidValue" | "multipleEnumTypes" | "invalidConstruct" | "missingEnumType", [ValidEnumTypeRuleOptions], unknown, RuleListener>;
|
|
180
|
+
};
|
|
161
181
|
|
|
162
182
|
// @alpha
|
|
163
183
|
export interface ValidEnumTypeRuleOptions {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
2
|
+
import { describe, it, expect, afterAll, afterEach, beforeEach } from 'vitest'
|
|
3
|
+
import * as tseslintParser from '@typescript-eslint/parser'
|
|
2
4
|
import * as path from 'path'
|
|
3
5
|
import * as fs from 'fs'
|
|
4
6
|
import * as os from 'os'
|
|
7
|
+
import { packageDocumentation } from '../../src/rules/package-documentation.js'
|
|
5
8
|
import {
|
|
6
9
|
findPackageJson,
|
|
7
10
|
isEntryPoint,
|
|
@@ -12,9 +15,49 @@ import {
|
|
|
12
15
|
parseTSDocComment,
|
|
13
16
|
} from '../../src/utils/tsdoc-parser.js'
|
|
14
17
|
|
|
18
|
+
// Wire up vitest for RuleTester
|
|
19
|
+
RuleTester.afterAll = afterAll
|
|
20
|
+
RuleTester.describe = describe
|
|
21
|
+
RuleTester.it = it
|
|
22
|
+
|
|
23
|
+
// Create a temp directory for RuleTester tests (must exist at module eval time)
|
|
24
|
+
const ruleTestDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-pd-rule-'))
|
|
25
|
+
const ruleTestSrcDir = path.join(ruleTestDir, 'src')
|
|
26
|
+
fs.mkdirSync(ruleTestSrcDir, { recursive: true })
|
|
27
|
+
fs.mkdirSync(path.join(ruleTestSrcDir, 'utils'), { recursive: true })
|
|
28
|
+
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.join(ruleTestDir, 'package.json'),
|
|
31
|
+
JSON.stringify({ name: 'test-package', main: './src/index.ts' }, null, 2),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Create placeholder files so paths resolve correctly
|
|
35
|
+
fs.writeFileSync(path.join(ruleTestSrcDir, 'index.ts'), '')
|
|
36
|
+
fs.writeFileSync(path.join(ruleTestSrcDir, 'helper.ts'), '')
|
|
37
|
+
fs.writeFileSync(path.join(ruleTestSrcDir, 'utils', 'deep.ts'), '')
|
|
38
|
+
|
|
39
|
+
const entryFile = path.join(ruleTestSrcDir, 'index.ts')
|
|
40
|
+
const helperFile = path.join(ruleTestSrcDir, 'helper.ts')
|
|
41
|
+
const deepFile = path.join(ruleTestSrcDir, 'utils', 'deep.ts')
|
|
42
|
+
|
|
43
|
+
const ruleTester = new RuleTester({
|
|
44
|
+
languageOptions: {
|
|
45
|
+
parser: tseslintParser,
|
|
46
|
+
parserOptions: {
|
|
47
|
+
ecmaVersion: 2022,
|
|
48
|
+
sourceType: 'module',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
15
53
|
describe('package-documentation', () => {
|
|
16
54
|
let tempDir: string
|
|
17
55
|
|
|
56
|
+
afterAll(() => {
|
|
57
|
+
fs.rmSync(ruleTestDir, { recursive: true, force: true })
|
|
58
|
+
clearPackageJsonCache()
|
|
59
|
+
})
|
|
60
|
+
|
|
18
61
|
beforeEach(() => {
|
|
19
62
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-plugin-test-'))
|
|
20
63
|
clearPackageJsonCache()
|
|
@@ -87,68 +130,72 @@ describe('package-documentation', () => {
|
|
|
87
130
|
})
|
|
88
131
|
})
|
|
89
132
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const code = `/**
|
|
97
|
-
* This is the main entry point.
|
|
133
|
+
ruleTester.run('package-documentation', packageDocumentation, {
|
|
134
|
+
valid: [
|
|
135
|
+
// Entry point with @packageDocumentation — should pass
|
|
136
|
+
{
|
|
137
|
+
code: `/**
|
|
138
|
+
* Package entry point.
|
|
98
139
|
* @packageDocumentation
|
|
99
140
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
141
|
+
export function foo() {}`,
|
|
142
|
+
filename: entryFile,
|
|
143
|
+
},
|
|
144
|
+
// Non-entry-point without @packageDocumentation — should pass
|
|
145
|
+
{
|
|
146
|
+
code: `/**
|
|
147
|
+
* A helper function.
|
|
148
|
+
*/
|
|
149
|
+
export function helper() {}`,
|
|
150
|
+
filename: helperFile,
|
|
151
|
+
},
|
|
152
|
+
// Non-entry-point with no comments at all — should pass
|
|
153
|
+
{
|
|
154
|
+
code: `export function helper() {}`,
|
|
155
|
+
filename: helperFile,
|
|
156
|
+
},
|
|
157
|
+
// Deep non-entry-point without @packageDocumentation — should pass
|
|
158
|
+
{
|
|
159
|
+
code: `export const x = 1`,
|
|
160
|
+
filename: deepFile,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
invalid: [
|
|
164
|
+
// Entry point without @packageDocumentation — should fail
|
|
165
|
+
{
|
|
166
|
+
code: `/**
|
|
167
|
+
* Missing package documentation.
|
|
168
|
+
*/
|
|
169
|
+
export function foo() {}`,
|
|
170
|
+
filename: entryFile,
|
|
171
|
+
errors: [{ messageId: 'missingPackageDocumentation' as const }],
|
|
172
|
+
},
|
|
173
|
+
// Entry point with no comments at all — should fail
|
|
174
|
+
{
|
|
175
|
+
code: `export function foo() {}`,
|
|
176
|
+
filename: entryFile,
|
|
177
|
+
errors: [{ messageId: 'missingPackageDocumentation' as const }],
|
|
178
|
+
},
|
|
179
|
+
// Non-entry-point with @packageDocumentation — should fail
|
|
180
|
+
{
|
|
181
|
+
code: `/**
|
|
182
|
+
* This shouldn't be here.
|
|
111
183
|
* @packageDocumentation
|
|
112
|
-
*/`)
|
|
113
|
-
expect(hasPackageDocumentation(parsed.docComment)).toBe(true)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('should fail when entry point lacks @packageDocumentation', () => {
|
|
117
|
-
createPackageJson('./src/index.ts')
|
|
118
|
-
const srcDir = createSourceDir()
|
|
119
|
-
const indexPath = path.join(srcDir, 'index.ts')
|
|
120
|
-
|
|
121
|
-
const code = `/**
|
|
122
|
-
* This is the main entry point.
|
|
123
184
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
it('should not require @packageDocumentation for non-entry points', () => {
|
|
140
|
-
createPackageJson('./src/index.ts')
|
|
141
|
-
const srcDir = createSourceDir()
|
|
142
|
-
|
|
143
|
-
const indexPath = path.join(srcDir, 'index.ts')
|
|
144
|
-
fs.writeFileSync(indexPath, '/** @packageDocumentation */ export {}')
|
|
145
|
-
|
|
146
|
-
const helperPath = path.join(srcDir, 'helper.ts')
|
|
147
|
-
fs.writeFileSync(helperPath, '// No package documentation needed')
|
|
148
|
-
|
|
149
|
-
const pkgPath = findPackageJson(srcDir)
|
|
150
|
-
// Helper is not an entry point, so no check needed
|
|
151
|
-
expect(isEntryPoint(helperPath, pkgPath!)).toBe(false)
|
|
152
|
-
})
|
|
185
|
+
export function helper() {}`,
|
|
186
|
+
filename: helperFile,
|
|
187
|
+
errors: [{ messageId: 'unexpectedPackageDocumentation' as const }],
|
|
188
|
+
},
|
|
189
|
+
// Deep non-entry-point with @packageDocumentation — should fail
|
|
190
|
+
{
|
|
191
|
+
code: `/**
|
|
192
|
+
* Wrong place for package docs.
|
|
193
|
+
* @packageDocumentation
|
|
194
|
+
*/
|
|
195
|
+
export const x = 1`,
|
|
196
|
+
filename: deepFile,
|
|
197
|
+
errors: [{ messageId: 'unexpectedPackageDocumentation' as const }],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
153
200
|
})
|
|
154
201
|
})
|