@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,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule for validating `@enumType` TSDoc tag usage.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This rule validates that `@enumType` tags are:
|
|
6
|
+
* - Only used on enum declarations or string literal union type aliases
|
|
7
|
+
* - Have a valid value ('open' or 'closed')
|
|
8
|
+
* - Not duplicated on a single declaration
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
|
|
14
|
+
import { getLeadingTSDocComment, extractEnumType } from '../utils/tsdoc-parser'
|
|
15
|
+
import type { ValidEnumTypeRuleOptions } from '../types'
|
|
16
|
+
|
|
17
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
18
|
+
(name) =>
|
|
19
|
+
`https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
type MessageIds =
|
|
23
|
+
| 'missingValue'
|
|
24
|
+
| 'invalidValue'
|
|
25
|
+
| 'multipleEnumTypes'
|
|
26
|
+
| 'invalidConstruct'
|
|
27
|
+
| 'missingEnumType'
|
|
28
|
+
|
|
29
|
+
export const validEnumType = createRule<[ValidEnumTypeRuleOptions], MessageIds>(
|
|
30
|
+
{
|
|
31
|
+
name: 'valid-enum-type',
|
|
32
|
+
meta: {
|
|
33
|
+
type: 'problem',
|
|
34
|
+
docs: {
|
|
35
|
+
description:
|
|
36
|
+
'Validate @enumType TSDoc tag usage on enums and string literal unions',
|
|
37
|
+
},
|
|
38
|
+
messages: {
|
|
39
|
+
missingValue: '@enumType tag requires a value of "open" or "closed"',
|
|
40
|
+
invalidValue:
|
|
41
|
+
'@enumType tag value "{{value}}" is invalid. Use "open" or "closed"',
|
|
42
|
+
multipleEnumTypes: 'Multiple @enumType tags found. Only one is allowed',
|
|
43
|
+
invalidConstruct:
|
|
44
|
+
'@enumType is only valid on enum declarations and string literal union type aliases',
|
|
45
|
+
missingEnumType:
|
|
46
|
+
'Exported {{kind}} "{{name}}" is missing @enumType tag. Add @enumType open or @enumType closed',
|
|
47
|
+
},
|
|
48
|
+
schema: [
|
|
49
|
+
{
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
requireOnExported: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
description:
|
|
55
|
+
'Require @enumType on all exported enums and string literal unions',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
defaultOptions: [{}],
|
|
63
|
+
create(context) {
|
|
64
|
+
const options = context.options[0] ?? {}
|
|
65
|
+
const requireOnExported = options.requireOnExported ?? false
|
|
66
|
+
|
|
67
|
+
const sourceCode = context.sourceCode
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the TSDoc comment text for a node, checking both the node and its export parent.
|
|
71
|
+
*/
|
|
72
|
+
function getTSDocComment(node: TSESTree.Node): string | undefined {
|
|
73
|
+
// First check the node itself
|
|
74
|
+
let commentText = getLeadingTSDocComment(sourceCode, node)
|
|
75
|
+
if (commentText) {
|
|
76
|
+
return commentText
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// If node is inside an export, check the export statement
|
|
80
|
+
const parent = node.parent
|
|
81
|
+
if (
|
|
82
|
+
parent?.type === AST_NODE_TYPES.ExportNamedDeclaration ||
|
|
83
|
+
parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration
|
|
84
|
+
) {
|
|
85
|
+
commentText = getLeadingTSDocComment(sourceCode, parent)
|
|
86
|
+
if (commentText) {
|
|
87
|
+
return commentText
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Checks if a node is exported.
|
|
96
|
+
*/
|
|
97
|
+
function isExported(node: TSESTree.Node): boolean {
|
|
98
|
+
const parent = node.parent
|
|
99
|
+
if (parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
if (parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Checks if a type alias is a string literal union.
|
|
110
|
+
* A string literal union is a union type where all members are string literal types.
|
|
111
|
+
*/
|
|
112
|
+
function isStringLiteralUnion(
|
|
113
|
+
typeAnnotation: TSESTree.TypeNode,
|
|
114
|
+
): boolean {
|
|
115
|
+
// Check if it's a union type
|
|
116
|
+
if (typeAnnotation.type !== AST_NODE_TYPES.TSUnionType) {
|
|
117
|
+
// Could be a single string literal - that's also valid
|
|
118
|
+
return (
|
|
119
|
+
typeAnnotation.type === AST_NODE_TYPES.TSLiteralType &&
|
|
120
|
+
typeAnnotation.literal.type === AST_NODE_TYPES.Literal &&
|
|
121
|
+
typeof typeAnnotation.literal.value === 'string'
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// All union members must be string literals
|
|
126
|
+
return typeAnnotation.types.every((member) => {
|
|
127
|
+
if (member.type === AST_NODE_TYPES.TSLiteralType) {
|
|
128
|
+
return (
|
|
129
|
+
member.literal.type === AST_NODE_TYPES.Literal &&
|
|
130
|
+
typeof member.literal.value === 'string'
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
return false
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validates `@enumType` usage on a node that should have it (enum or string literal union).
|
|
139
|
+
*/
|
|
140
|
+
function validateEnumTypeTag(
|
|
141
|
+
node: TSESTree.TSEnumDeclaration | TSESTree.TSTypeAliasDeclaration,
|
|
142
|
+
kind: 'enum' | 'type',
|
|
143
|
+
): void {
|
|
144
|
+
const commentText = getTSDocComment(node)
|
|
145
|
+
const name = node.id.name
|
|
146
|
+
|
|
147
|
+
if (!commentText) {
|
|
148
|
+
// No TSDoc comment - check if we should require `@enumType`
|
|
149
|
+
if (requireOnExported && isExported(node)) {
|
|
150
|
+
context.report({
|
|
151
|
+
node,
|
|
152
|
+
messageId: 'missingEnumType',
|
|
153
|
+
data: { kind, name },
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const extraction = extractEnumType(commentText)
|
|
160
|
+
|
|
161
|
+
if (!extraction.found) {
|
|
162
|
+
// No @enumType tag - check if we should require it
|
|
163
|
+
if (requireOnExported && isExported(node)) {
|
|
164
|
+
context.report({
|
|
165
|
+
node,
|
|
166
|
+
messageId: 'missingEnumType',
|
|
167
|
+
data: { kind, name },
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Multiple @enumType tags
|
|
174
|
+
if (extraction.count > 1) {
|
|
175
|
+
context.report({
|
|
176
|
+
node,
|
|
177
|
+
messageId: 'multipleEnumTypes',
|
|
178
|
+
})
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// @enumType without value
|
|
183
|
+
if (!extraction.rawValue) {
|
|
184
|
+
context.report({
|
|
185
|
+
node,
|
|
186
|
+
messageId: 'missingValue',
|
|
187
|
+
})
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Invalid value
|
|
192
|
+
if (!extraction.isValid) {
|
|
193
|
+
context.report({
|
|
194
|
+
node,
|
|
195
|
+
messageId: 'invalidValue',
|
|
196
|
+
data: { value: extraction.rawValue },
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Checks for `@enumType` on invalid constructs (non-enum, non-string-literal-union).
|
|
203
|
+
*/
|
|
204
|
+
function checkInvalidEnumTypeUsage(node: TSESTree.Node): void {
|
|
205
|
+
const commentText = getTSDocComment(node)
|
|
206
|
+
if (!commentText) {
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const extraction = extractEnumType(commentText)
|
|
211
|
+
if (extraction.found) {
|
|
212
|
+
context.report({
|
|
213
|
+
node,
|
|
214
|
+
messageId: 'invalidConstruct',
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
// Check enum declarations - @enumType is valid here
|
|
221
|
+
TSEnumDeclaration(node): void {
|
|
222
|
+
validateEnumTypeTag(node, 'enum')
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// Check type alias declarations
|
|
226
|
+
TSTypeAliasDeclaration(node): void {
|
|
227
|
+
if (isStringLiteralUnion(node.typeAnnotation)) {
|
|
228
|
+
// This is a string literal union - @enumType is valid
|
|
229
|
+
validateEnumTypeTag(node, 'type')
|
|
230
|
+
} else {
|
|
231
|
+
// Not a string literal union - @enumType is invalid
|
|
232
|
+
checkInvalidEnumTypeUsage(node)
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// Check invalid constructs - @enumType should not be on these
|
|
237
|
+
FunctionDeclaration(node): void {
|
|
238
|
+
checkInvalidEnumTypeUsage(node)
|
|
239
|
+
},
|
|
240
|
+
ClassDeclaration(node): void {
|
|
241
|
+
checkInvalidEnumTypeUsage(node)
|
|
242
|
+
},
|
|
243
|
+
TSInterfaceDeclaration(node): void {
|
|
244
|
+
checkInvalidEnumTypeUsage(node)
|
|
245
|
+
},
|
|
246
|
+
VariableDeclaration(node): void {
|
|
247
|
+
checkInvalidEnumTypeUsage(node)
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
)
|
package/src/types.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the API Extractor ESLint plugin.
|
|
3
3
|
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* All types in this module are isomorphic - they work in both Node.js and browser environments.
|
|
6
|
+
*
|
|
4
7
|
* @packageDocumentation
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Log levels supported by API Extractor message configuration.
|
|
9
|
-
* @
|
|
12
|
+
* @alpha
|
|
10
13
|
*/
|
|
11
14
|
export type ApiExtractorLogLevel = 'error' | 'warning' | 'none'
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* Configuration for a single message type in API Extractor.
|
|
15
|
-
* @
|
|
18
|
+
* @alpha
|
|
16
19
|
*/
|
|
17
20
|
export interface MessageConfig {
|
|
18
21
|
logLevel: ApiExtractorLogLevel
|
|
@@ -21,7 +24,7 @@ export interface MessageConfig {
|
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* The messages configuration section from api-extractor.json.
|
|
24
|
-
* @
|
|
27
|
+
* @alpha
|
|
25
28
|
*/
|
|
26
29
|
export interface ApiExtractorMessagesConfig {
|
|
27
30
|
compilerMessageReporting?: {
|
|
@@ -34,6 +37,7 @@ export interface ApiExtractorMessagesConfig {
|
|
|
34
37
|
'ae-forgotten-export'?: MessageConfig
|
|
35
38
|
'ae-internal-missing-underscore'?: MessageConfig
|
|
36
39
|
'ae-incompatible-release-tags'?: MessageConfig
|
|
40
|
+
'ae-extra-release-tag'?: MessageConfig
|
|
37
41
|
[messageId: string]: MessageConfig | undefined
|
|
38
42
|
}
|
|
39
43
|
tsdocMessageReporting?: {
|
|
@@ -44,7 +48,7 @@ export interface ApiExtractorMessagesConfig {
|
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* Partial representation of api-extractor.json relevant for this plugin.
|
|
47
|
-
* @
|
|
51
|
+
* @alpha
|
|
48
52
|
*/
|
|
49
53
|
export interface ApiExtractorConfig {
|
|
50
54
|
extends?: string
|
|
@@ -54,13 +58,13 @@ export interface ApiExtractorConfig {
|
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
* Release tags recognized by API Extractor.
|
|
57
|
-
* @
|
|
61
|
+
* @alpha
|
|
58
62
|
*/
|
|
59
63
|
export type ReleaseTag = 'public' | 'beta' | 'alpha' | 'internal'
|
|
60
64
|
|
|
61
65
|
/**
|
|
62
66
|
* All valid release tags.
|
|
63
|
-
* @
|
|
67
|
+
* @alpha
|
|
64
68
|
*/
|
|
65
69
|
export const RELEASE_TAGS: readonly ReleaseTag[] = [
|
|
66
70
|
'public',
|
|
@@ -71,34 +75,73 @@ export const RELEASE_TAGS: readonly ReleaseTag[] = [
|
|
|
71
75
|
|
|
72
76
|
/**
|
|
73
77
|
* Options for the missing-release-tag rule.
|
|
74
|
-
*
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* All options are explicit - no automatic file discovery.
|
|
81
|
+
* Node.js users can use the `/node` entry point utilities to read config from disk.
|
|
82
|
+
*
|
|
83
|
+
* @alpha
|
|
75
84
|
*/
|
|
76
85
|
export interface MissingReleaseTagRuleOptions {
|
|
77
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Severity level for missing release tags.
|
|
88
|
+
* - 'error': Report as error
|
|
89
|
+
* - 'warning': Report as warning (default)
|
|
90
|
+
* - 'none': Disable the check
|
|
91
|
+
*
|
|
92
|
+
* @defaultValue 'warning'
|
|
93
|
+
*/
|
|
94
|
+
severity?: ApiExtractorLogLevel
|
|
78
95
|
}
|
|
79
96
|
|
|
80
97
|
/**
|
|
81
98
|
* Options for the override-keyword rule.
|
|
82
|
-
*
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* This rule is purely syntactic and requires no configuration.
|
|
102
|
+
*
|
|
103
|
+
* @alpha
|
|
83
104
|
*/
|
|
84
|
-
export
|
|
85
|
-
configPath?: string
|
|
86
|
-
}
|
|
105
|
+
export type OverrideKeywordRuleOptions = Record<string, never>
|
|
87
106
|
|
|
88
107
|
/**
|
|
89
108
|
* Options for the package-documentation rule.
|
|
90
|
-
*
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
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
|
+
*
|
|
115
|
+
* @alpha
|
|
91
116
|
*/
|
|
92
|
-
export
|
|
93
|
-
configPath?: string
|
|
94
|
-
}
|
|
117
|
+
export type PackageDocumentationRuleOptions = Record<string, never>
|
|
95
118
|
|
|
96
119
|
/**
|
|
97
120
|
* Resolved entry points from package.json.
|
|
98
|
-
* @
|
|
121
|
+
* @alpha
|
|
99
122
|
*/
|
|
100
123
|
export interface ResolvedEntryPoints {
|
|
101
124
|
main?: string
|
|
102
125
|
types?: string
|
|
103
126
|
exports: string[]
|
|
104
127
|
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Options for the valid-enum-type rule.
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* This rule validates `@enumType` TSDoc tag usage on enum declarations
|
|
134
|
+
* and string literal union type aliases.
|
|
135
|
+
*
|
|
136
|
+
* @alpha
|
|
137
|
+
*/
|
|
138
|
+
export interface ValidEnumTypeRuleOptions {
|
|
139
|
+
/**
|
|
140
|
+
* Whether to require `@enumType` on all exported enums and string literal unions.
|
|
141
|
+
* - When true, missing `@enumType` tags on exported enums/unions will be reported
|
|
142
|
+
* - When false (default), only invalid usage is reported
|
|
143
|
+
*
|
|
144
|
+
* @defaultValue false
|
|
145
|
+
*/
|
|
146
|
+
requireOnExported?: boolean
|
|
147
|
+
}
|
package/src/utils/entry-point.ts
CHANGED
|
@@ -37,6 +37,7 @@ function getParser(): TSDocParser {
|
|
|
37
37
|
*
|
|
38
38
|
* @param commentText - The full comment text including delimiters
|
|
39
39
|
* @returns Parser context with the parsed doc comment
|
|
40
|
+
* @alpha
|
|
40
41
|
*/
|
|
41
42
|
export function parseTSDocComment(commentText: string): ParserContext {
|
|
42
43
|
const parser = getParser()
|
|
@@ -49,6 +50,7 @@ export function parseTSDocComment(commentText: string): ParserContext {
|
|
|
49
50
|
*
|
|
50
51
|
* @param docComment - The parsed doc comment
|
|
51
52
|
* @returns The release tag if found, undefined otherwise
|
|
53
|
+
* @alpha
|
|
52
54
|
*/
|
|
53
55
|
export function extractReleaseTag(
|
|
54
56
|
docComment: DocComment,
|
|
@@ -74,6 +76,7 @@ export function extractReleaseTag(
|
|
|
74
76
|
*
|
|
75
77
|
* @param docComment - The parsed doc comment
|
|
76
78
|
* @returns True if @override tag is present
|
|
79
|
+
* @alpha
|
|
77
80
|
*/
|
|
78
81
|
export function hasOverrideTag(docComment: DocComment): boolean {
|
|
79
82
|
return docComment.modifierTagSet.isOverride()
|
|
@@ -84,11 +87,73 @@ export function hasOverrideTag(docComment: DocComment): boolean {
|
|
|
84
87
|
*
|
|
85
88
|
* @param docComment - The parsed doc comment
|
|
86
89
|
* @returns True if @packageDocumentation tag is present
|
|
90
|
+
* @alpha
|
|
87
91
|
*/
|
|
88
92
|
export function hasPackageDocumentation(docComment: DocComment): boolean {
|
|
89
93
|
return docComment.modifierTagSet.isPackageDocumentation()
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Enum type values for `@enumType` tag.
|
|
98
|
+
* @alpha
|
|
99
|
+
*/
|
|
100
|
+
export type EnumTypeValue = 'open' | 'closed'
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Result of extracting `@enumType` tag from a TSDoc comment.
|
|
104
|
+
* @alpha
|
|
105
|
+
*/
|
|
106
|
+
export interface EnumTypeExtraction {
|
|
107
|
+
/** Whether any `@enumType` tags were found */
|
|
108
|
+
found: boolean
|
|
109
|
+
/** Number of `@enumType` tags found */
|
|
110
|
+
count: number
|
|
111
|
+
/** The extracted value (lowercase), or undefined if missing/invalid */
|
|
112
|
+
value?: string
|
|
113
|
+
/** Whether the value is valid ('open' or 'closed') */
|
|
114
|
+
isValid: boolean
|
|
115
|
+
/** The original raw value from the comment */
|
|
116
|
+
rawValue?: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Extracts `@enumType` tag information from a TSDoc comment.
|
|
121
|
+
*
|
|
122
|
+
* @param commentText - The full comment text including delimiters
|
|
123
|
+
* @returns Extraction result with tag information
|
|
124
|
+
* @alpha
|
|
125
|
+
*/
|
|
126
|
+
export function extractEnumType(commentText: string): EnumTypeExtraction {
|
|
127
|
+
const result: EnumTypeExtraction = {
|
|
128
|
+
found: false,
|
|
129
|
+
count: 0,
|
|
130
|
+
isValid: false,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Match @enumType followed by optional whitespace and a word (letters/numbers only)
|
|
134
|
+
// The value must be a word-like identifier, not * or other punctuation
|
|
135
|
+
// We use [ \t]+ instead of \s+ to avoid capturing across newlines
|
|
136
|
+
const enumTypeRegex = /@enumType(?:[ \t]+([a-zA-Z][a-zA-Z0-9]*))?/gi
|
|
137
|
+
let match: RegExpExecArray | null
|
|
138
|
+
|
|
139
|
+
while ((match = enumTypeRegex.exec(commentText)) !== null) {
|
|
140
|
+
result.count++
|
|
141
|
+
result.found = true
|
|
142
|
+
|
|
143
|
+
// Only capture the first occurrence's value
|
|
144
|
+
if (result.count === 1) {
|
|
145
|
+
result.rawValue = match[1]
|
|
146
|
+
if (match[1]) {
|
|
147
|
+
const lowerValue = match[1].toLowerCase()
|
|
148
|
+
result.value = lowerValue
|
|
149
|
+
result.isValid = lowerValue === 'open' || lowerValue === 'closed'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result
|
|
155
|
+
}
|
|
156
|
+
|
|
92
157
|
/**
|
|
93
158
|
* Checks if a comment is a block comment (TSDoc style).
|
|
94
159
|
*/
|
|
@@ -104,6 +169,7 @@ function isBlockComment(comment: TSESTree.Comment): boolean {
|
|
|
104
169
|
* @param sourceCode - ESLint source code object
|
|
105
170
|
* @param node - The AST node to check
|
|
106
171
|
* @returns The comment text if a TSDoc comment exists, undefined otherwise
|
|
172
|
+
* @alpha
|
|
107
173
|
*/
|
|
108
174
|
export function getLeadingTSDocComment(
|
|
109
175
|
sourceCode: {
|
|
@@ -142,6 +208,7 @@ export function getLeadingTSDocComment(
|
|
|
142
208
|
*
|
|
143
209
|
* @param sourceCode - ESLint source code object
|
|
144
210
|
* @returns Array of comment objects with their parsed content
|
|
211
|
+
* @alpha
|
|
145
212
|
*/
|
|
146
213
|
export function findAllTSDocComments(sourceCode: {
|
|
147
214
|
getAllComments: () => TSESTree.Comment[]
|