@api-extractor-tools/eslint-plugin 0.1.0-alpha.0

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +183 -0
  4. package/api-extractor.json +10 -0
  5. package/dist/configs/index.d.ts +6 -0
  6. package/dist/configs/index.d.ts.map +1 -0
  7. package/dist/configs/index.js +11 -0
  8. package/dist/configs/index.js.map +1 -0
  9. package/dist/configs/recommended.d.ts +31 -0
  10. package/dist/configs/recommended.d.ts.map +1 -0
  11. package/dist/configs/recommended.js +45 -0
  12. package/dist/configs/recommended.js.map +1 -0
  13. package/dist/index.d.ts +74 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +68 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/rules/index.d.ts +14 -0
  18. package/dist/rules/index.d.ts.map +1 -0
  19. package/dist/rules/index.js +20 -0
  20. package/dist/rules/index.js.map +1 -0
  21. package/dist/rules/missing-release-tag.d.ts +8 -0
  22. package/dist/rules/missing-release-tag.d.ts.map +1 -0
  23. package/dist/rules/missing-release-tag.js +148 -0
  24. package/dist/rules/missing-release-tag.js.map +1 -0
  25. package/dist/rules/override-keyword.d.ts +8 -0
  26. package/dist/rules/override-keyword.d.ts.map +1 -0
  27. package/dist/rules/override-keyword.js +106 -0
  28. package/dist/rules/override-keyword.js.map +1 -0
  29. package/dist/rules/package-documentation.d.ts +8 -0
  30. package/dist/rules/package-documentation.d.ts.map +1 -0
  31. package/dist/rules/package-documentation.js +70 -0
  32. package/dist/rules/package-documentation.js.map +1 -0
  33. package/dist/types.d.ts +90 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +19 -0
  36. package/dist/types.js.map +1 -0
  37. package/dist/utils/config-loader.d.ts +47 -0
  38. package/dist/utils/config-loader.d.ts.map +1 -0
  39. package/dist/utils/config-loader.js +163 -0
  40. package/dist/utils/config-loader.js.map +1 -0
  41. package/dist/utils/entry-point.d.ts +56 -0
  42. package/dist/utils/entry-point.d.ts.map +1 -0
  43. package/dist/utils/entry-point.js +198 -0
  44. package/dist/utils/entry-point.js.map +1 -0
  45. package/dist/utils/index.d.ts +8 -0
  46. package/dist/utils/index.d.ts.map +1 -0
  47. package/dist/utils/index.js +21 -0
  48. package/dist/utils/index.js.map +1 -0
  49. package/dist/utils/tsdoc-parser.d.ts +58 -0
  50. package/dist/utils/tsdoc-parser.d.ts.map +1 -0
  51. package/dist/utils/tsdoc-parser.js +137 -0
  52. package/dist/utils/tsdoc-parser.js.map +1 -0
  53. package/package.json +44 -0
  54. package/src/configs/index.ts +6 -0
  55. package/src/configs/recommended.ts +46 -0
  56. package/src/index.ts +111 -0
  57. package/src/rules/index.ts +18 -0
  58. package/src/rules/missing-release-tag.ts +203 -0
  59. package/src/rules/override-keyword.ts +139 -0
  60. package/src/rules/package-documentation.ts +90 -0
  61. package/src/types.ts +104 -0
  62. package/src/utils/config-loader.ts +194 -0
  63. package/src/utils/entry-point.ts +247 -0
  64. package/src/utils/index.ts +17 -0
  65. package/src/utils/tsdoc-parser.ts +163 -0
  66. package/temp/eslint-plugin.api.md +118 -0
  67. package/test/index.test.ts +66 -0
  68. package/test/rules/missing-release-tag.test.ts +184 -0
  69. package/test/rules/override-keyword.test.ts +171 -0
  70. package/test/rules/package-documentation.test.ts +152 -0
  71. package/test/tsconfig.json +11 -0
  72. package/test/utils/config-loader.test.ts +199 -0
  73. package/test/utils/entry-point.test.ts +172 -0
  74. package/test/utils/tsdoc-parser.test.ts +113 -0
  75. package/tsconfig.json +12 -0
  76. package/vitest.config.mts +25 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsdoc-parser.js","sourceRoot":"","sources":["../../src/utils/tsdoc-parser.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@api-extractor-tools/eslint-plugin",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "ESLint plugin providing authoring-time feedback for API Extractor",
5
+ "main": "dist/index.js",
6
+ "types": "dist/eslint-plugin-public.d.ts",
7
+ "keywords": [
8
+ "eslint",
9
+ "eslint-plugin",
10
+ "api-extractor",
11
+ "tsdoc",
12
+ "typescript"
13
+ ],
14
+ "author": "",
15
+ "license": "MIT",
16
+ "peerDependencies": {
17
+ "eslint": ">=8.0.0",
18
+ "typescript": ">=5.5.0"
19
+ },
20
+ "dependencies": {
21
+ "@microsoft/tsdoc": "^0.15.1",
22
+ "@typescript-eslint/utils": "^8.48.1"
23
+ },
24
+ "devDependencies": {
25
+ "@microsoft/api-extractor": "^7.55.1",
26
+ "@types/node": "^22.10.2",
27
+ "@typescript-eslint/parser": "^8.48.1",
28
+ "@typescript-eslint/rule-tester": "^8.48.1",
29
+ "eslint": "^9.39.1",
30
+ "typescript": "5.8.3",
31
+ "@vitest/coverage-v8": "^4.0.15",
32
+ "vitest": "^4.0.15"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "generate:api-report": "api-extractor run --local --verbose",
37
+ "test": "vitest run",
38
+ "test:coverage": "vitest run --coverage",
39
+ "check": "pnpm check:eslint && pnpm check:typecheck-tests && pnpm check:api-report",
40
+ "check:eslint": "eslint src",
41
+ "check:typecheck-tests": "tsc -p test/tsconfig.json",
42
+ "check:api-report": "api-extractor run"
43
+ }
44
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Configuration exports.
3
+ * @internal
4
+ */
5
+
6
+ export { recommendedRules, createFlatRecommendedConfig } from './recommended'
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Recommended ESLint configuration for API Extractor.
3
+ *
4
+ * @remarks
5
+ * This configuration works with both flat config (eslint.config.js) and
6
+ * legacy config (.eslintrc.js) formats.
7
+ *
8
+ * @internal
9
+ */
10
+
11
+ import type { TSESLint } from '@typescript-eslint/utils'
12
+
13
+ /**
14
+ * Recommended rule configuration.
15
+ * These are the rules enabled by default with appropriate severity.
16
+ * @public
17
+ */
18
+ export const recommendedRules: TSESLint.Linter.RulesRecord = {
19
+ '@api-extractor-tools/missing-release-tag': 'warn',
20
+ '@api-extractor-tools/override-keyword': 'error',
21
+ '@api-extractor-tools/package-documentation': 'warn',
22
+ }
23
+
24
+ /**
25
+ * Flat-config recommended configuration.
26
+ *
27
+ * @remarks
28
+ * Use with eslint.config.js:
29
+ * ```js
30
+ * import apiExtractorPlugin from '@api-extractor-tools/eslint-plugin';
31
+ *
32
+ * export default [
33
+ * apiExtractorPlugin.configs.recommended,
34
+ * ];
35
+ * ```
36
+ */
37
+ export function createFlatRecommendedConfig(
38
+ plugin: Record<string, unknown>,
39
+ ): TSESLint.FlatConfig.Config {
40
+ return {
41
+ plugins: {
42
+ '@api-extractor-tools': plugin as TSESLint.FlatConfig.Plugin,
43
+ },
44
+ rules: recommendedRules,
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ESLint plugin providing authoring-time feedback for API Extractor.
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
+ * @example
9
+ * Using with flat config (eslint.config.js):
10
+ * ```js
11
+ * import apiExtractorPlugin from '@api-extractor-tools/eslint-plugin';
12
+ *
13
+ * export default [
14
+ * 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
+ * ];
25
+ * ```
26
+ *
27
+ * @example
28
+ * Using with legacy config (.eslintrc.js):
29
+ * ```js
30
+ * module.exports = {
31
+ * plugins: ['@api-extractor-tools'],
32
+ * extends: ['plugin:@api-extractor-tools/recommended-legacy'],
33
+ * };
34
+ * ```
35
+ *
36
+ * @packageDocumentation
37
+ */
38
+
39
+ import type { TSESLint } from '@typescript-eslint/utils'
40
+ import { rules } from './rules'
41
+ import { recommendedRules, createFlatRecommendedConfig } from './configs'
42
+
43
+ // Re-export types
44
+ export type {
45
+ ApiExtractorConfig,
46
+ ApiExtractorLogLevel,
47
+ ApiExtractorMessagesConfig,
48
+ MessageConfig,
49
+ ReleaseTag,
50
+ MissingReleaseTagRuleOptions,
51
+ OverrideKeywordRuleOptions,
52
+ PackageDocumentationRuleOptions,
53
+ ResolvedEntryPoints,
54
+ } from './types'
55
+
56
+ export { RELEASE_TAGS } from './types'
57
+
58
+ /**
59
+ * Plugin configuration type.
60
+ * @internal
61
+ */
62
+ interface PluginConfigs {
63
+ recommended: TSESLint.FlatConfig.Config
64
+ 'recommended-legacy': {
65
+ plugins: string[]
66
+ rules: TSESLint.Linter.RulesRecord
67
+ }
68
+ }
69
+
70
+ /**
71
+ * The ESLint plugin type.
72
+ * @public
73
+ */
74
+ export interface ApiExtractorEslintPlugin {
75
+ meta: {
76
+ name: string
77
+ version: string
78
+ }
79
+ rules: typeof rules
80
+ configs: PluginConfigs
81
+ }
82
+
83
+ /**
84
+ * The ESLint plugin object.
85
+ * @public
86
+ */
87
+ const plugin: ApiExtractorEslintPlugin = {
88
+ meta: {
89
+ name: '@api-extractor-tools/eslint-plugin',
90
+ version: '0.0.1',
91
+ },
92
+ rules,
93
+ configs: {
94
+ recommended: null as unknown as TSESLint.FlatConfig.Config,
95
+ 'recommended-legacy': {
96
+ plugins: ['@api-extractor-tools'],
97
+ rules: recommendedRules,
98
+ },
99
+ },
100
+ }
101
+
102
+ // Add flat config after plugin is defined (needed for self-reference)
103
+ plugin.configs.recommended = createFlatRecommendedConfig(
104
+ plugin as unknown as Record<string, unknown>,
105
+ )
106
+
107
+ export default plugin
108
+
109
+ // Named exports for CommonJS compatibility
110
+ export { rules }
111
+ export { recommendedRules }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ESLint rules for API Extractor.
3
+ * @internal
4
+ */
5
+
6
+ import { missingReleaseTag } from './missing-release-tag'
7
+ import { overrideKeyword } from './override-keyword'
8
+ import { packageDocumentation } from './package-documentation'
9
+
10
+ /**
11
+ * All available ESLint rules.
12
+ * @public
13
+ */
14
+ export const rules = {
15
+ 'missing-release-tag': missingReleaseTag,
16
+ 'override-keyword': overrideKeyword,
17
+ 'package-documentation': packageDocumentation,
18
+ } as const
@@ -0,0 +1,203 @@
1
+ /**
2
+ * ESLint rule for detecting missing release tags on exported symbols.
3
+ * @internal
4
+ */
5
+
6
+ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
7
+ import {
8
+ resolveConfig,
9
+ getMessageLogLevel,
10
+ getLeadingTSDocComment,
11
+ parseTSDocComment,
12
+ extractReleaseTag,
13
+ } from '../utils'
14
+ import type { MissingReleaseTagRuleOptions } from '../types'
15
+
16
+ const createRule = ESLintUtils.RuleCreator(
17
+ (name) =>
18
+ `https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`,
19
+ )
20
+
21
+ type MessageIds = 'missingReleaseTag'
22
+
23
+ export const missingReleaseTag = createRule<
24
+ [MissingReleaseTagRuleOptions],
25
+ MessageIds
26
+ >({
27
+ name: 'missing-release-tag',
28
+ meta: {
29
+ type: 'problem',
30
+ docs: {
31
+ description:
32
+ 'Require exported symbols to have a release tag (@public, @beta, @alpha, or @internal)',
33
+ },
34
+ messages: {
35
+ missingReleaseTag:
36
+ 'Exported symbol "{{name}}" is missing a release tag. Add @public, @beta, @alpha, or @internal to its TSDoc comment.',
37
+ },
38
+ schema: [
39
+ {
40
+ type: 'object',
41
+ properties: {
42
+ configPath: {
43
+ type: 'string',
44
+ description: 'Path to api-extractor.json configuration file',
45
+ },
46
+ },
47
+ additionalProperties: false,
48
+ },
49
+ ],
50
+ },
51
+ defaultOptions: [{}],
52
+ create(context) {
53
+ const options = context.options[0] ?? {}
54
+ const filename = context.filename
55
+ const config = resolveConfig(filename, options.configPath)
56
+ const logLevel = getMessageLogLevel(config, 'ae-missing-release-tag')
57
+
58
+ // If configured to 'none', disable the rule
59
+ if (logLevel === 'none') {
60
+ return {}
61
+ }
62
+
63
+ const sourceCode = context.sourceCode
64
+
65
+ /**
66
+ * Checks if a node has a release tag in its TSDoc comment.
67
+ */
68
+ function hasReleaseTag(node: TSESTree.Node): boolean {
69
+ const commentText = getLeadingTSDocComment(sourceCode, node)
70
+ if (!commentText) {
71
+ return false
72
+ }
73
+
74
+ const parsed = parseTSDocComment(commentText)
75
+ if (parsed.docComment) {
76
+ const tag = extractReleaseTag(parsed.docComment)
77
+ return tag !== undefined
78
+ }
79
+
80
+ return false
81
+ }
82
+
83
+ /**
84
+ * Gets the name of a declaration.
85
+ */
86
+ function getDeclarationName(
87
+ node:
88
+ | TSESTree.FunctionDeclaration
89
+ | TSESTree.ClassDeclaration
90
+ | TSESTree.TSInterfaceDeclaration
91
+ | TSESTree.TSTypeAliasDeclaration
92
+ | TSESTree.TSEnumDeclaration
93
+ | TSESTree.VariableDeclaration,
94
+ ): string {
95
+ if (node.type === AST_NODE_TYPES.VariableDeclaration) {
96
+ const firstDeclarator = node.declarations[0]
97
+ if (firstDeclarator?.id.type === AST_NODE_TYPES.Identifier) {
98
+ return firstDeclarator.id.name
99
+ }
100
+ return '<unknown>'
101
+ }
102
+
103
+ if ('id' in node && node.id) {
104
+ return node.id.name
105
+ }
106
+
107
+ return '<anonymous>'
108
+ }
109
+
110
+ /**
111
+ * Reports a missing release tag error.
112
+ */
113
+ function reportMissingTag(node: TSESTree.Node, name: string): void {
114
+ context.report({
115
+ node,
116
+ messageId: 'missingReleaseTag',
117
+ data: { name },
118
+ })
119
+ }
120
+
121
+ /**
122
+ * Checks if a node is exported.
123
+ */
124
+ function isExported(node: TSESTree.Node): boolean {
125
+ const parent = node.parent
126
+
127
+ // Direct export: export function foo() {}
128
+ if (parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) {
129
+ return true
130
+ }
131
+
132
+ // Default export: export default function foo() {}
133
+ if (parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration) {
134
+ return true
135
+ }
136
+
137
+ return false
138
+ }
139
+
140
+ /**
141
+ * Checks a declaration node for missing release tag.
142
+ */
143
+ function checkDeclaration(
144
+ node:
145
+ | TSESTree.FunctionDeclaration
146
+ | TSESTree.ClassDeclaration
147
+ | TSESTree.TSInterfaceDeclaration
148
+ | TSESTree.TSTypeAliasDeclaration
149
+ | TSESTree.TSEnumDeclaration
150
+ | TSESTree.VariableDeclaration,
151
+ ): void {
152
+ if (!isExported(node)) {
153
+ return
154
+ }
155
+
156
+ // For exported declarations, check the export statement for the comment
157
+ const exportNode = node.parent
158
+ const nodeToCheck = exportNode ?? node
159
+
160
+ if (!hasReleaseTag(nodeToCheck) && !hasReleaseTag(node)) {
161
+ const name = getDeclarationName(node)
162
+ reportMissingTag(node, name)
163
+ }
164
+ }
165
+
166
+ return {
167
+ // Check exported function declarations
168
+ FunctionDeclaration(node): void {
169
+ checkDeclaration(node)
170
+ },
171
+
172
+ // Check exported class declarations
173
+ ClassDeclaration(node): void {
174
+ checkDeclaration(node)
175
+ },
176
+
177
+ // Check exported interface declarations
178
+ TSInterfaceDeclaration(node): void {
179
+ checkDeclaration(node)
180
+ },
181
+
182
+ // Check exported type alias declarations
183
+ TSTypeAliasDeclaration(node): void {
184
+ checkDeclaration(node)
185
+ },
186
+
187
+ // Check exported enum declarations
188
+ TSEnumDeclaration(node): void {
189
+ checkDeclaration(node)
190
+ },
191
+
192
+ // Check exported variable declarations
193
+ VariableDeclaration(node): void {
194
+ checkDeclaration(node)
195
+ },
196
+
197
+ // Note: We intentionally don't handle ExportNamedDeclaration with specifiers
198
+ // (e.g., `export { foo }` or `export { foo } from './bar'`) because:
199
+ // - Re-exports should have release tags on the original declaration
200
+ // - Named exports reference declarations that are checked elsewhere
201
+ }
202
+ },
203
+ })
@@ -0,0 +1,139 @@
1
+ /**
2
+ * ESLint rule requiring the TypeScript `override` keyword when `@override` TSDoc tag is used.
3
+ * @internal
4
+ */
5
+
6
+ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
7
+ import {
8
+ getLeadingTSDocComment,
9
+ parseTSDocComment,
10
+ hasOverrideTag,
11
+ } from '../utils'
12
+ import type { OverrideKeywordRuleOptions } from '../types'
13
+
14
+ const createRule = ESLintUtils.RuleCreator(
15
+ (name) =>
16
+ `https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`,
17
+ )
18
+
19
+ type MessageIds = 'missingOverrideKeyword'
20
+
21
+ export const overrideKeyword = createRule<
22
+ [OverrideKeywordRuleOptions],
23
+ MessageIds
24
+ >({
25
+ name: 'override-keyword',
26
+ meta: {
27
+ type: 'problem',
28
+ docs: {
29
+ description:
30
+ 'Require the TypeScript `override` keyword when the `@override` TSDoc tag is present',
31
+ },
32
+ fixable: 'code',
33
+ messages: {
34
+ missingOverrideKeyword:
35
+ 'Member "{{name}}" has @override TSDoc tag but is missing the TypeScript `override` keyword.',
36
+ },
37
+ schema: [
38
+ {
39
+ type: 'object',
40
+ properties: {
41
+ configPath: {
42
+ type: 'string',
43
+ description: 'Path to api-extractor.json configuration file',
44
+ },
45
+ },
46
+ additionalProperties: false,
47
+ },
48
+ ],
49
+ },
50
+ defaultOptions: [{}],
51
+ create(context) {
52
+ const sourceCode = context.sourceCode
53
+
54
+ /**
55
+ * Checks if a node has the @override TSDoc tag.
56
+ */
57
+ function nodeHasOverrideTag(node: TSESTree.Node): boolean {
58
+ const commentText = getLeadingTSDocComment(sourceCode, node)
59
+ if (!commentText) {
60
+ return false
61
+ }
62
+
63
+ const parsed = parseTSDocComment(commentText)
64
+ if (parsed.docComment) {
65
+ return hasOverrideTag(parsed.docComment)
66
+ }
67
+
68
+ return false
69
+ }
70
+
71
+ /**
72
+ * Checks if a method or property has the override keyword.
73
+ */
74
+ function hasOverrideModifier(
75
+ node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition,
76
+ ): boolean {
77
+ return node.override === true
78
+ }
79
+
80
+ /**
81
+ * Gets the name of a class member.
82
+ */
83
+ function getMemberName(
84
+ node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition,
85
+ ): string {
86
+ if (node.key.type === AST_NODE_TYPES.Identifier) {
87
+ return node.key.name
88
+ }
89
+ if (node.key.type === AST_NODE_TYPES.Literal) {
90
+ return String(node.key.value)
91
+ }
92
+ return '<computed>'
93
+ }
94
+
95
+ /**
96
+ * Checks a class member for missing override keyword.
97
+ */
98
+ function checkMember(
99
+ node: TSESTree.MethodDefinition | TSESTree.PropertyDefinition,
100
+ ): void {
101
+ // Skip constructors
102
+ if (
103
+ node.type === AST_NODE_TYPES.MethodDefinition &&
104
+ node.kind === 'constructor'
105
+ ) {
106
+ return
107
+ }
108
+
109
+ if (!nodeHasOverrideTag(node)) {
110
+ return
111
+ }
112
+
113
+ if (hasOverrideModifier(node)) {
114
+ return
115
+ }
116
+
117
+ const name = getMemberName(node)
118
+
119
+ context.report({
120
+ node,
121
+ messageId: 'missingOverrideKeyword',
122
+ data: { name },
123
+ fix(fixer) {
124
+ // Insert override keyword at the start of the member declaration
125
+ return fixer.insertTextBefore(node, 'override ')
126
+ },
127
+ })
128
+ }
129
+
130
+ return {
131
+ MethodDefinition(node): void {
132
+ checkMember(node)
133
+ },
134
+ PropertyDefinition(node): void {
135
+ checkMember(node)
136
+ },
137
+ }
138
+ },
139
+ })
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ESLint rule requiring @packageDocumentation in package entry point files.
3
+ * @internal
4
+ */
5
+
6
+ import { ESLintUtils } from '@typescript-eslint/utils'
7
+ import {
8
+ findPackageJson,
9
+ isEntryPoint,
10
+ findAllTSDocComments,
11
+ hasPackageDocumentation,
12
+ } from '../utils'
13
+ import type { PackageDocumentationRuleOptions } from '../types'
14
+
15
+ const createRule = ESLintUtils.RuleCreator(
16
+ (name) =>
17
+ `https://github.com/mike-north/api-extractor-tools/blob/main/tools/eslint-plugin/docs/rules/${name}.md`,
18
+ )
19
+
20
+ type MessageIds = 'missingPackageDocumentation'
21
+
22
+ export const packageDocumentation = createRule<
23
+ [PackageDocumentationRuleOptions],
24
+ MessageIds
25
+ >({
26
+ name: 'package-documentation',
27
+ meta: {
28
+ type: 'suggestion',
29
+ docs: {
30
+ description:
31
+ 'Require @packageDocumentation tag in package entry point files',
32
+ },
33
+ messages: {
34
+ missingPackageDocumentation:
35
+ 'Entry point file is missing a @packageDocumentation comment. Add a TSDoc comment with @packageDocumentation at the top of the file.',
36
+ },
37
+ schema: [
38
+ {
39
+ type: 'object',
40
+ properties: {
41
+ configPath: {
42
+ type: 'string',
43
+ description: 'Path to api-extractor.json configuration file',
44
+ },
45
+ },
46
+ additionalProperties: false,
47
+ },
48
+ ],
49
+ },
50
+ defaultOptions: [{}],
51
+ create(context) {
52
+ const filename = context.filename
53
+ const sourceCode = context.sourceCode
54
+
55
+ // Find package.json
56
+ const pkgPath = findPackageJson(filename)
57
+ if (!pkgPath) {
58
+ // No package.json found, nothing to check
59
+ return {}
60
+ }
61
+
62
+ // Check if this file is an entry point
63
+ if (!isEntryPoint(filename, pkgPath)) {
64
+ // Not an entry point, skip
65
+ return {}
66
+ }
67
+
68
+ return {
69
+ Program(node): void {
70
+ // Look for @packageDocumentation in any TSDoc comment in the file
71
+ const tsdocComments = findAllTSDocComments(sourceCode)
72
+
73
+ for (const { parsed } of tsdocComments) {
74
+ if (parsed.docComment && hasPackageDocumentation(parsed.docComment)) {
75
+ // Found @packageDocumentation, all good
76
+ return
77
+ }
78
+ }
79
+
80
+ // No @packageDocumentation found
81
+ // Report at the first line of the file
82
+ context.report({
83
+ node,
84
+ loc: { line: 1, column: 0 },
85
+ messageId: 'missingPackageDocumentation',
86
+ })
87
+ },
88
+ }
89
+ },
90
+ })