@api-extractor-tools/eslint-plugin 0.1.0-alpha.0 → 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 +204 -0
- package/CHANGELOG.md +32 -0
- package/README.md +306 -10
- package/api-extractor.json +1 -0
- package/dist/configs/recommended.d.ts +1 -1
- package/dist/configs/recommended.d.ts.map +1 -1
- package/dist/configs/recommended.js +7 -1
- package/dist/configs/recommended.js.map +1 -1
- package/dist/index.d.ts +9 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -15
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +28 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +41 -0
- package/dist/node.js.map +1 -0
- package/dist/rules/extra-release-tag.d.ts +24 -0
- package/dist/rules/extra-release-tag.d.ts.map +1 -0
- package/dist/rules/extra-release-tag.js +141 -0
- package/dist/rules/extra-release-tag.js.map +1 -0
- package/dist/rules/forgotten-export.d.ts +24 -0
- package/dist/rules/forgotten-export.d.ts.map +1 -0
- package/dist/rules/forgotten-export.js +212 -0
- package/dist/rules/forgotten-export.js.map +1 -0
- package/dist/rules/incompatible-release-tags.d.ts +25 -0
- package/dist/rules/incompatible-release-tags.d.ts.map +1 -0
- package/dist/rules/incompatible-release-tags.js +237 -0
- package/dist/rules/incompatible-release-tags.js.map +1 -0
- package/dist/rules/index.d.ts +8 -2
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +13 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/missing-release-tag.d.ts +4 -0
- package/dist/rules/missing-release-tag.d.ts.map +1 -1
- package/dist/rules/missing-release-tag.js +14 -21
- package/dist/rules/missing-release-tag.js.map +1 -1
- package/dist/rules/override-keyword.d.ts +4 -0
- package/dist/rules/override-keyword.d.ts.map +1 -1
- package/dist/rules/override-keyword.js +9 -11
- package/dist/rules/override-keyword.js.map +1 -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 +45 -35
- package/dist/rules/package-documentation.js.map +1 -1
- package/dist/rules/public-on-non-exported.d.ts +24 -0
- package/dist/rules/public-on-non-exported.d.ts.map +1 -0
- package/dist/rules/public-on-non-exported.js +191 -0
- package/dist/rules/public-on-non-exported.js.map +1 -0
- package/dist/rules/public-on-private-member.d.ts +24 -0
- package/dist/rules/public-on-private-member.d.ts.map +1 -0
- package/dist/rules/public-on-private-member.js +111 -0
- package/dist/rules/public-on-private-member.js.map +1 -0
- package/dist/rules/valid-enum-type.d.ts +17 -0
- package/dist/rules/valid-enum-type.d.ts.map +1 -0
- package/dist/rules/valid-enum-type.js +206 -0
- package/dist/rules/valid-enum-type.js.map +1 -0
- package/dist/types.d.ts +66 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/tsdoc-parser.d.ts +37 -6
- package/dist/utils/tsdoc-parser.d.ts.map +1 -1
- package/dist/utils/tsdoc-parser.js +40 -0
- package/dist/utils/tsdoc-parser.js.map +1 -1
- package/docs/rules/valid-enum-type.md +153 -0
- package/package.json +22 -8
- package/src/configs/recommended.ts +7 -1
- package/src/index.ts +21 -15
- package/src/node.ts +50 -0
- package/src/rules/extra-release-tag.ts +201 -0
- package/src/rules/forgotten-export.ts +274 -0
- package/src/rules/incompatible-release-tags.ts +331 -0
- package/src/rules/index.ts +13 -1
- package/src/rules/missing-release-tag.ts +11 -26
- package/src/rules/override-keyword.ts +6 -8
- package/src/rules/package-documentation.ts +54 -40
- package/src/rules/public-on-non-exported.ts +265 -0
- package/src/rules/public-on-private-member.ts +157 -0
- package/src/rules/valid-enum-type.ts +252 -0
- package/src/types.ts +60 -17
- package/src/utils/config-loader.ts +1 -0
- package/src/utils/entry-point.ts +1 -0
- package/src/utils/tsdoc-parser.ts +67 -0
- package/temp/eslint-plugin.api.md +101 -32
- package/test/index.test.ts +1 -0
- package/test/rules/extra-release-tag.test.ts +276 -0
- package/test/rules/forgotten-export.test.ts +190 -0
- package/test/rules/incompatible-release-tags.test.ts +340 -0
- package/test/rules/missing-release-tag.test.ts +2 -1
- package/test/rules/override-keyword.test.ts +2 -1
- package/test/rules/package-documentation.test.ts +113 -64
- package/test/rules/public-on-non-exported.test.ts +201 -0
- package/test/rules/public-on-private-member.test.ts +207 -0
- package/test/rules/valid-enum-type.test.ts +409 -0
- package/test/types.test-d.ts +20 -0
- package/test/utils/config-loader.test.ts +1 -0
- package/test/utils/tsdoc-parser.test.ts +117 -9
- package/tsconfig.json +1 -0
- package/vitest.config.mts +1 -0
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -21
- package/dist/utils/index.js.map +0 -1
- package/src/utils/index.ts +0 -17
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# valid-enum-type
|
|
2
|
+
|
|
3
|
+
Validates the usage of the `@enumType` TSDoc tag on enum declarations and string literal union type aliases.
|
|
4
|
+
|
|
5
|
+
## Rule Details
|
|
6
|
+
|
|
7
|
+
The `@enumType` tag is used to specify whether an enum or string literal union is "open" (new members may be added) or "closed" (the set of members is fixed). This information is used by the change detector to properly classify API changes.
|
|
8
|
+
|
|
9
|
+
This rule ensures that:
|
|
10
|
+
|
|
11
|
+
- `@enumType` tags have a valid value (`open` or `closed`)
|
|
12
|
+
- `@enumType` tags are not duplicated
|
|
13
|
+
- `@enumType` tags are only used on valid constructs (enums and string literal unions)
|
|
14
|
+
- Optionally, exported enums and string literal unions have an `@enumType` tag
|
|
15
|
+
|
|
16
|
+
## Options
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"@api-extractor-tools/valid-enum-type": [
|
|
21
|
+
"warn",
|
|
22
|
+
{
|
|
23
|
+
"requireOnExported": false
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `requireOnExported`
|
|
30
|
+
|
|
31
|
+
Type: `boolean`
|
|
32
|
+
Default: `false`
|
|
33
|
+
|
|
34
|
+
When `true`, requires all exported enums and string literal union type aliases to have an `@enumType` tag.
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
### ❌ Incorrect
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// Missing value
|
|
42
|
+
/**
|
|
43
|
+
* @enumType
|
|
44
|
+
*/
|
|
45
|
+
export enum Status {
|
|
46
|
+
Active,
|
|
47
|
+
Inactive,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Invalid value
|
|
51
|
+
/**
|
|
52
|
+
* @enumType invalid
|
|
53
|
+
*/
|
|
54
|
+
export enum Status {
|
|
55
|
+
Active,
|
|
56
|
+
Inactive,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Multiple @enumType tags
|
|
60
|
+
/**
|
|
61
|
+
* @enumType open
|
|
62
|
+
* @enumType closed
|
|
63
|
+
*/
|
|
64
|
+
export enum Status {
|
|
65
|
+
Active,
|
|
66
|
+
Inactive,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// @enumType on invalid construct
|
|
70
|
+
/**
|
|
71
|
+
* @enumType open
|
|
72
|
+
*/
|
|
73
|
+
export function myFunction(): void {}
|
|
74
|
+
|
|
75
|
+
// @enumType on non-string-literal union
|
|
76
|
+
/**
|
|
77
|
+
* @enumType open
|
|
78
|
+
*/
|
|
79
|
+
export type NumberUnion = 1 | 2 | 3
|
|
80
|
+
|
|
81
|
+
// With requireOnExported: true - missing @enumType
|
|
82
|
+
export enum Status {
|
|
83
|
+
Active,
|
|
84
|
+
Inactive,
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### ✅ Correct
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// Open enum - new members may be added in future versions
|
|
92
|
+
/**
|
|
93
|
+
* Status values for a resource.
|
|
94
|
+
* @enumType open
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
export enum Status {
|
|
98
|
+
Active = 'active',
|
|
99
|
+
Inactive = 'inactive',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Closed enum - the set of members is fixed
|
|
103
|
+
/**
|
|
104
|
+
* Boolean-like values.
|
|
105
|
+
* @enumType closed
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
export enum YesNo {
|
|
109
|
+
Yes = 'yes',
|
|
110
|
+
No = 'no',
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// String literal union with @enumType
|
|
114
|
+
/**
|
|
115
|
+
* Supported color values.
|
|
116
|
+
* @enumType open
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
export type Color = 'red' | 'green' | 'blue'
|
|
120
|
+
|
|
121
|
+
// Single string literal (also valid)
|
|
122
|
+
/**
|
|
123
|
+
* The only supported format.
|
|
124
|
+
* @enumType closed
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
export type Format = 'json'
|
|
128
|
+
|
|
129
|
+
// Without requireOnExported, missing @enumType is allowed
|
|
130
|
+
export enum InternalStatus {
|
|
131
|
+
Pending,
|
|
132
|
+
Complete,
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## When to Use
|
|
137
|
+
|
|
138
|
+
Use this rule when:
|
|
139
|
+
|
|
140
|
+
- You use the `@enumType` tag to document enum semantics
|
|
141
|
+
- You want to ensure consistent and valid `@enumType` usage
|
|
142
|
+
- You want to require `@enumType` on all exported enums (with `requireOnExported: true`)
|
|
143
|
+
|
|
144
|
+
## When Not to Use
|
|
145
|
+
|
|
146
|
+
You might want to disable this rule if:
|
|
147
|
+
|
|
148
|
+
- You don't use the `@enumType` tag in your codebase
|
|
149
|
+
- You're not using the change detector that processes this tag
|
|
150
|
+
|
|
151
|
+
## Related
|
|
152
|
+
|
|
153
|
+
- [Open/Closed Enum Semantics](https://github.com/mike-north/api-extractor-tools/issues/127) - Epic describing the enum type feature
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
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",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/eslint-plugin-public.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./node": {
|
|
13
|
+
"types": "./dist/node.d.ts",
|
|
14
|
+
"default": "./dist/node.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"keywords": [
|
|
8
18
|
"eslint",
|
|
9
19
|
"eslint-plugin",
|
|
@@ -18,26 +28,30 @@
|
|
|
18
28
|
"typescript": ">=5.5.0"
|
|
19
29
|
},
|
|
20
30
|
"dependencies": {
|
|
21
|
-
"@microsoft/tsdoc": "^0.15.1"
|
|
22
|
-
"@typescript-eslint/utils": "^8.48.1"
|
|
31
|
+
"@microsoft/tsdoc": "^0.15.1"
|
|
23
32
|
},
|
|
24
33
|
"devDependencies": {
|
|
25
34
|
"@microsoft/api-extractor": "^7.55.1",
|
|
26
35
|
"@types/node": "^22.10.2",
|
|
27
36
|
"@typescript-eslint/parser": "^8.48.1",
|
|
28
37
|
"@typescript-eslint/rule-tester": "^8.48.1",
|
|
38
|
+
"@typescript-eslint/utils": "^8.48.1",
|
|
39
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
29
40
|
"eslint": "^9.39.1",
|
|
41
|
+
"tsd": "^0.33.0",
|
|
30
42
|
"typescript": "5.8.3",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
43
|
+
"vitest": "^4.0.15",
|
|
44
|
+
"@api-extractor-tools/declaration-file-normalizer": "0.1.0-alpha.6"
|
|
33
45
|
},
|
|
34
46
|
"scripts": {
|
|
35
|
-
"
|
|
47
|
+
"clean": "rm -rf dist",
|
|
48
|
+
"build": "tsc && node ../declaration-file-normalizer/dist/cli.js dist/index.d.ts",
|
|
36
49
|
"generate:api-report": "api-extractor run --local --verbose",
|
|
37
|
-
"test": "vitest run",
|
|
50
|
+
"test": "vitest run && pnpm test:types",
|
|
51
|
+
"test:types": "tsd --files 'test/**/*.test-d.ts' --typings src/index.ts",
|
|
38
52
|
"test:coverage": "vitest run --coverage",
|
|
39
53
|
"check": "pnpm check:eslint && pnpm check:typecheck-tests && pnpm check:api-report",
|
|
40
|
-
"check:eslint": "eslint src",
|
|
54
|
+
"check:eslint": "eslint src test",
|
|
41
55
|
"check:typecheck-tests": "tsc -p test/tsconfig.json",
|
|
42
56
|
"check:api-report": "api-extractor run"
|
|
43
57
|
}
|
|
@@ -13,12 +13,18 @@ import type { TSESLint } from '@typescript-eslint/utils'
|
|
|
13
13
|
/**
|
|
14
14
|
* Recommended rule configuration.
|
|
15
15
|
* These are the rules enabled by default with appropriate severity.
|
|
16
|
-
* @
|
|
16
|
+
* @alpha
|
|
17
17
|
*/
|
|
18
18
|
export const recommendedRules: TSESLint.Linter.RulesRecord = {
|
|
19
19
|
'@api-extractor-tools/missing-release-tag': 'warn',
|
|
20
20
|
'@api-extractor-tools/override-keyword': 'error',
|
|
21
21
|
'@api-extractor-tools/package-documentation': 'warn',
|
|
22
|
+
'@api-extractor-tools/forgotten-export': 'warn',
|
|
23
|
+
'@api-extractor-tools/incompatible-release-tags': 'warn',
|
|
24
|
+
'@api-extractor-tools/extra-release-tag': 'error',
|
|
25
|
+
'@api-extractor-tools/public-on-private-member': 'error',
|
|
26
|
+
'@api-extractor-tools/public-on-non-exported': 'error',
|
|
27
|
+
'@api-extractor-tools/valid-enum-type': 'warn',
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ESLint plugin providing authoring-time feedback for API Extractor.
|
|
3
3
|
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* This plugin provides ESLint rules that mirror API Extractor's validations,
|
|
6
|
-
* enabling developers to catch issues during development rather than at build time.
|
|
7
|
-
*
|
|
8
4
|
* @example
|
|
9
5
|
* Using with flat config (eslint.config.js):
|
|
10
6
|
* ```js
|
|
@@ -12,15 +8,6 @@
|
|
|
12
8
|
*
|
|
13
9
|
* export default [
|
|
14
10
|
* apiExtractorPlugin.configs.recommended,
|
|
15
|
-
* // Or configure rules individually:
|
|
16
|
-
* {
|
|
17
|
-
* plugins: {
|
|
18
|
-
* '@api-extractor-tools': apiExtractorPlugin,
|
|
19
|
-
* },
|
|
20
|
-
* rules: {
|
|
21
|
-
* '@api-extractor-tools/missing-release-tag': 'error',
|
|
22
|
-
* },
|
|
23
|
-
* },
|
|
24
11
|
* ];
|
|
25
12
|
* ```
|
|
26
13
|
*
|
|
@@ -51,10 +38,29 @@ export type {
|
|
|
51
38
|
OverrideKeywordRuleOptions,
|
|
52
39
|
PackageDocumentationRuleOptions,
|
|
53
40
|
ResolvedEntryPoints,
|
|
41
|
+
ValidEnumTypeRuleOptions,
|
|
54
42
|
} from './types'
|
|
43
|
+
export type { ForgottenExportRuleOptions } from './rules/forgotten-export'
|
|
44
|
+
export type { IncompatibleReleaseTagsRuleOptions } from './rules/incompatible-release-tags'
|
|
45
|
+
export type { ExtraReleaseTagRuleOptions } from './rules/extra-release-tag'
|
|
46
|
+
export type { PublicOnPrivateMemberRuleOptions } from './rules/public-on-private-member'
|
|
47
|
+
export type { PublicOnNonExportedRuleOptions } from './rules/public-on-non-exported'
|
|
55
48
|
|
|
56
49
|
export { RELEASE_TAGS } from './types'
|
|
57
50
|
|
|
51
|
+
// Re-export TSDoc utilities (browser-safe)
|
|
52
|
+
export {
|
|
53
|
+
parseTSDocComment,
|
|
54
|
+
extractReleaseTag,
|
|
55
|
+
hasOverrideTag,
|
|
56
|
+
hasPackageDocumentation,
|
|
57
|
+
getLeadingTSDocComment,
|
|
58
|
+
findAllTSDocComments,
|
|
59
|
+
extractEnumType,
|
|
60
|
+
type EnumTypeValue,
|
|
61
|
+
type EnumTypeExtraction,
|
|
62
|
+
} from './utils/tsdoc-parser'
|
|
63
|
+
|
|
58
64
|
/**
|
|
59
65
|
* Plugin configuration type.
|
|
60
66
|
* @internal
|
|
@@ -69,7 +75,7 @@ interface PluginConfigs {
|
|
|
69
75
|
|
|
70
76
|
/**
|
|
71
77
|
* The ESLint plugin type.
|
|
72
|
-
* @
|
|
78
|
+
* @alpha
|
|
73
79
|
*/
|
|
74
80
|
export interface ApiExtractorEslintPlugin {
|
|
75
81
|
meta: {
|
|
@@ -82,7 +88,7 @@ export interface ApiExtractorEslintPlugin {
|
|
|
82
88
|
|
|
83
89
|
/**
|
|
84
90
|
* The ESLint plugin object.
|
|
85
|
-
* @
|
|
91
|
+
* @alpha
|
|
86
92
|
*/
|
|
87
93
|
const plugin: ApiExtractorEslintPlugin = {
|
|
88
94
|
meta: {
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js-specific utilities for the ESLint plugin.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* These utilities require Node.js file system access. Use them to:
|
|
6
|
+
* - Load api-extractor.json configuration
|
|
7
|
+
* - Discover configuration file locations
|
|
8
|
+
* - Determine package entry points
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {
|
|
13
|
+
* findApiExtractorConfig,
|
|
14
|
+
* loadApiExtractorConfig,
|
|
15
|
+
* getMessageLogLevel,
|
|
16
|
+
* } from '@api-extractor-tools/eslint-plugin/node';
|
|
17
|
+
*
|
|
18
|
+
* const configPath = findApiExtractorConfig(__dirname);
|
|
19
|
+
* const config = configPath ? loadApiExtractorConfig(configPath) : null;
|
|
20
|
+
* const severity = getMessageLogLevel(config, 'ae-missing-release-tag');
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
findApiExtractorConfig,
|
|
28
|
+
loadApiExtractorConfig,
|
|
29
|
+
resolveConfig,
|
|
30
|
+
getMessageLogLevel,
|
|
31
|
+
logLevelToSeverity,
|
|
32
|
+
clearConfigCache,
|
|
33
|
+
} from './utils/config-loader'
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
findPackageJson,
|
|
37
|
+
loadPackageJson,
|
|
38
|
+
resolveEntryPoints,
|
|
39
|
+
isEntryPoint,
|
|
40
|
+
clearPackageJsonCache,
|
|
41
|
+
} from './utils/entry-point'
|
|
42
|
+
|
|
43
|
+
// Re-export types that are useful for Node.js consumers
|
|
44
|
+
export type {
|
|
45
|
+
ApiExtractorConfig,
|
|
46
|
+
ApiExtractorLogLevel,
|
|
47
|
+
ApiExtractorMessagesConfig,
|
|
48
|
+
MessageConfig,
|
|
49
|
+
ResolvedEntryPoints,
|
|
50
|
+
} from './types'
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule for detecting multiple release tags on a single symbol.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This rule detects when a symbol has more than one release tag
|
|
6
|
+
* (e.g., both `@public` and `@beta`). Each symbol should have exactly one release tag.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
|
|
12
|
+
import {
|
|
13
|
+
getLeadingTSDocComment,
|
|
14
|
+
parseTSDocComment,
|
|
15
|
+
} from '../utils/tsdoc-parser'
|
|
16
|
+
import type { ApiExtractorLogLevel } from '../types'
|
|
17
|
+
|
|
18
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
19
|
+
(name) =>
|
|
20
|
+
`https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
type MessageIds = 'extraReleaseTag'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for the extra-release-tag rule.
|
|
27
|
+
* @alpha
|
|
28
|
+
*/
|
|
29
|
+
export interface ExtraReleaseTagRuleOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Severity level for extra release tags.
|
|
32
|
+
* @defaultValue 'error'
|
|
33
|
+
*/
|
|
34
|
+
severity?: ApiExtractorLogLevel
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const extraReleaseTag = createRule<
|
|
38
|
+
[ExtraReleaseTagRuleOptions],
|
|
39
|
+
MessageIds
|
|
40
|
+
>({
|
|
41
|
+
name: 'extra-release-tag',
|
|
42
|
+
meta: {
|
|
43
|
+
type: 'problem',
|
|
44
|
+
docs: {
|
|
45
|
+
description:
|
|
46
|
+
'Require that symbols have at most one release tag (@public, @beta, @alpha, or @internal)',
|
|
47
|
+
},
|
|
48
|
+
messages: {
|
|
49
|
+
extraReleaseTag:
|
|
50
|
+
'Symbol "{{name}}" has multiple release tags: {{tags}}. Each symbol should have exactly one release tag.',
|
|
51
|
+
},
|
|
52
|
+
schema: [
|
|
53
|
+
{
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
severity: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
enum: ['error', 'warning', 'none'],
|
|
59
|
+
description: 'Severity level for extra release tags',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
defaultOptions: [{}],
|
|
67
|
+
create(context) {
|
|
68
|
+
const options = context.options[0] ?? {}
|
|
69
|
+
const severity = options.severity ?? 'error'
|
|
70
|
+
|
|
71
|
+
// If severity is 'none', disable the rule
|
|
72
|
+
if (severity === 'none') {
|
|
73
|
+
return {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const sourceCode = context.sourceCode
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Counts release tags in a TSDoc comment.
|
|
80
|
+
*/
|
|
81
|
+
function countReleaseTags(node: TSESTree.Node): {
|
|
82
|
+
count: number
|
|
83
|
+
tags: string[]
|
|
84
|
+
} {
|
|
85
|
+
const commentText = getLeadingTSDocComment(sourceCode, node)
|
|
86
|
+
if (!commentText) {
|
|
87
|
+
return { count: 0, tags: [] }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parsed = parseTSDocComment(commentText)
|
|
91
|
+
if (!parsed.docComment) {
|
|
92
|
+
return { count: 0, tags: [] }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const modifierTagSet = parsed.docComment.modifierTagSet
|
|
96
|
+
const tags: string[] = []
|
|
97
|
+
|
|
98
|
+
if (modifierTagSet.isPublic()) {
|
|
99
|
+
tags.push('@public')
|
|
100
|
+
}
|
|
101
|
+
if (modifierTagSet.isBeta()) {
|
|
102
|
+
tags.push('@beta')
|
|
103
|
+
}
|
|
104
|
+
if (modifierTagSet.isAlpha()) {
|
|
105
|
+
tags.push('@alpha')
|
|
106
|
+
}
|
|
107
|
+
if (modifierTagSet.isInternal()) {
|
|
108
|
+
tags.push('@internal')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { count: tags.length, tags }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gets the name of a declaration.
|
|
116
|
+
*/
|
|
117
|
+
function getDeclarationName(
|
|
118
|
+
node:
|
|
119
|
+
| TSESTree.FunctionDeclaration
|
|
120
|
+
| TSESTree.ClassDeclaration
|
|
121
|
+
| TSESTree.TSInterfaceDeclaration
|
|
122
|
+
| TSESTree.TSTypeAliasDeclaration
|
|
123
|
+
| TSESTree.TSEnumDeclaration
|
|
124
|
+
| TSESTree.VariableDeclaration,
|
|
125
|
+
): string {
|
|
126
|
+
if (node.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
127
|
+
const firstDeclarator = node.declarations[0]
|
|
128
|
+
if (firstDeclarator?.id.type === AST_NODE_TYPES.Identifier) {
|
|
129
|
+
return firstDeclarator.id.name
|
|
130
|
+
}
|
|
131
|
+
return '<unknown>'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if ('id' in node && node.id) {
|
|
135
|
+
return node.id.name
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return '<anonymous>'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Checks a declaration for multiple release tags.
|
|
143
|
+
*/
|
|
144
|
+
function checkDeclaration(
|
|
145
|
+
node:
|
|
146
|
+
| TSESTree.FunctionDeclaration
|
|
147
|
+
| TSESTree.ClassDeclaration
|
|
148
|
+
| TSESTree.TSInterfaceDeclaration
|
|
149
|
+
| TSESTree.TSTypeAliasDeclaration
|
|
150
|
+
| TSESTree.TSEnumDeclaration
|
|
151
|
+
| TSESTree.VariableDeclaration,
|
|
152
|
+
): void {
|
|
153
|
+
// Check both the export statement and the declaration for the comment
|
|
154
|
+
const exportNode = node.parent
|
|
155
|
+
let releaseTagInfo = countReleaseTags(node)
|
|
156
|
+
|
|
157
|
+
// If no tags on the declaration, check the export statement
|
|
158
|
+
if (
|
|
159
|
+
releaseTagInfo.count === 0 &&
|
|
160
|
+
exportNode &&
|
|
161
|
+
(exportNode.type === AST_NODE_TYPES.ExportNamedDeclaration ||
|
|
162
|
+
exportNode.type === AST_NODE_TYPES.ExportDefaultDeclaration)
|
|
163
|
+
) {
|
|
164
|
+
releaseTagInfo = countReleaseTags(exportNode)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (releaseTagInfo.count > 1) {
|
|
168
|
+
const name = getDeclarationName(node)
|
|
169
|
+
context.report({
|
|
170
|
+
node,
|
|
171
|
+
messageId: 'extraReleaseTag',
|
|
172
|
+
data: {
|
|
173
|
+
name,
|
|
174
|
+
tags: releaseTagInfo.tags.join(', '),
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
FunctionDeclaration(node): void {
|
|
182
|
+
checkDeclaration(node)
|
|
183
|
+
},
|
|
184
|
+
ClassDeclaration(node): void {
|
|
185
|
+
checkDeclaration(node)
|
|
186
|
+
},
|
|
187
|
+
TSInterfaceDeclaration(node): void {
|
|
188
|
+
checkDeclaration(node)
|
|
189
|
+
},
|
|
190
|
+
TSTypeAliasDeclaration(node): void {
|
|
191
|
+
checkDeclaration(node)
|
|
192
|
+
},
|
|
193
|
+
TSEnumDeclaration(node): void {
|
|
194
|
+
checkDeclaration(node)
|
|
195
|
+
},
|
|
196
|
+
VariableDeclaration(node): void {
|
|
197
|
+
checkDeclaration(node)
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
})
|