@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.
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/api-extractor.json +10 -0
- package/dist/configs/index.d.ts +6 -0
- package/dist/configs/index.d.ts.map +1 -0
- package/dist/configs/index.js +11 -0
- package/dist/configs/index.js.map +1 -0
- package/dist/configs/recommended.d.ts +31 -0
- package/dist/configs/recommended.d.ts.map +1 -0
- package/dist/configs/recommended.js +45 -0
- package/dist/configs/recommended.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/index.d.ts +14 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +20 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/missing-release-tag.d.ts +8 -0
- package/dist/rules/missing-release-tag.d.ts.map +1 -0
- package/dist/rules/missing-release-tag.js +148 -0
- package/dist/rules/missing-release-tag.js.map +1 -0
- package/dist/rules/override-keyword.d.ts +8 -0
- package/dist/rules/override-keyword.d.ts.map +1 -0
- package/dist/rules/override-keyword.js +106 -0
- package/dist/rules/override-keyword.js.map +1 -0
- package/dist/rules/package-documentation.d.ts +8 -0
- package/dist/rules/package-documentation.d.ts.map +1 -0
- package/dist/rules/package-documentation.js +70 -0
- package/dist/rules/package-documentation.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config-loader.d.ts +47 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +163 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/entry-point.d.ts +56 -0
- package/dist/utils/entry-point.d.ts.map +1 -0
- package/dist/utils/entry-point.js +198 -0
- package/dist/utils/entry-point.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/tsdoc-parser.d.ts +58 -0
- package/dist/utils/tsdoc-parser.d.ts.map +1 -0
- package/dist/utils/tsdoc-parser.js +137 -0
- package/dist/utils/tsdoc-parser.js.map +1 -0
- package/package.json +44 -0
- package/src/configs/index.ts +6 -0
- package/src/configs/recommended.ts +46 -0
- package/src/index.ts +111 -0
- package/src/rules/index.ts +18 -0
- package/src/rules/missing-release-tag.ts +203 -0
- package/src/rules/override-keyword.ts +139 -0
- package/src/rules/package-documentation.ts +90 -0
- package/src/types.ts +104 -0
- package/src/utils/config-loader.ts +194 -0
- package/src/utils/entry-point.ts +247 -0
- package/src/utils/index.ts +17 -0
- package/src/utils/tsdoc-parser.ts +163 -0
- package/temp/eslint-plugin.api.md +118 -0
- package/test/index.test.ts +66 -0
- package/test/rules/missing-release-tag.test.ts +184 -0
- package/test/rules/override-keyword.test.ts +171 -0
- package/test/rules/package-documentation.test.ts +152 -0
- package/test/tsconfig.json +11 -0
- package/test/utils/config-loader.test.ts +199 -0
- package/test/utils/entry-point.test.ts +172 -0
- package/test/utils/tsdoc-parser.test.ts +113 -0
- package/tsconfig.json +12 -0
- package/vitest.config.mts +25 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as os from 'os'
|
|
5
|
+
import { clearPackageJsonCache } from '../../src/utils/entry-point.js'
|
|
6
|
+
import {
|
|
7
|
+
findPackageJson,
|
|
8
|
+
isEntryPoint,
|
|
9
|
+
hasPackageDocumentation,
|
|
10
|
+
parseTSDocComment,
|
|
11
|
+
} from '../../src/utils'
|
|
12
|
+
|
|
13
|
+
describe('package-documentation', () => {
|
|
14
|
+
let tempDir: string
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-plugin-test-'))
|
|
18
|
+
clearPackageJsonCache()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
23
|
+
clearPackageJsonCache()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function createPackageJson(main: string): void {
|
|
27
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
pkgPath,
|
|
30
|
+
JSON.stringify({ name: 'test-package', main }, null, 2),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createSourceDir(): string {
|
|
35
|
+
const srcDir = path.join(tempDir, 'src')
|
|
36
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
37
|
+
return srcDir
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('entry point detection', () => {
|
|
41
|
+
it('should correctly identify entry points from package.json', () => {
|
|
42
|
+
createPackageJson('./src/index.ts')
|
|
43
|
+
const srcDir = createSourceDir()
|
|
44
|
+
const indexPath = path.join(srcDir, 'index.ts')
|
|
45
|
+
fs.writeFileSync(indexPath, 'export {}')
|
|
46
|
+
|
|
47
|
+
const pkgPath = findPackageJson(srcDir)
|
|
48
|
+
expect(pkgPath).toBeDefined()
|
|
49
|
+
expect(isEntryPoint(indexPath, pkgPath!)).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should not identify non-entry points', () => {
|
|
53
|
+
createPackageJson('./src/index.ts')
|
|
54
|
+
const srcDir = createSourceDir()
|
|
55
|
+
|
|
56
|
+
const indexPath = path.join(srcDir, 'index.ts')
|
|
57
|
+
fs.writeFileSync(indexPath, 'export {}')
|
|
58
|
+
|
|
59
|
+
const helperPath = path.join(srcDir, 'helper.ts')
|
|
60
|
+
fs.writeFileSync(helperPath, 'export {}')
|
|
61
|
+
|
|
62
|
+
const pkgPath = findPackageJson(srcDir)
|
|
63
|
+
expect(isEntryPoint(helperPath, pkgPath!)).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('@packageDocumentation detection', () => {
|
|
68
|
+
it('should detect @packageDocumentation in a comment', () => {
|
|
69
|
+
const comment = `/**
|
|
70
|
+
* This is the main entry point.
|
|
71
|
+
* @packageDocumentation
|
|
72
|
+
*/`
|
|
73
|
+
const parsed = parseTSDocComment(comment)
|
|
74
|
+
expect(parsed.docComment).toBeDefined()
|
|
75
|
+
expect(hasPackageDocumentation(parsed.docComment!)).toBe(true)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should return false when @packageDocumentation is missing', () => {
|
|
79
|
+
const comment = `/**
|
|
80
|
+
* This is just a comment.
|
|
81
|
+
*/`
|
|
82
|
+
const parsed = parseTSDocComment(comment)
|
|
83
|
+
expect(parsed.docComment).toBeDefined()
|
|
84
|
+
expect(hasPackageDocumentation(parsed.docComment!)).toBe(false)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('rule logic simulation', () => {
|
|
89
|
+
it('should pass when entry point has @packageDocumentation', () => {
|
|
90
|
+
createPackageJson('./src/index.ts')
|
|
91
|
+
const srcDir = createSourceDir()
|
|
92
|
+
const indexPath = path.join(srcDir, 'index.ts')
|
|
93
|
+
|
|
94
|
+
const code = `/**
|
|
95
|
+
* This is the main entry point.
|
|
96
|
+
* @packageDocumentation
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
export function foo() {}`
|
|
100
|
+
|
|
101
|
+
fs.writeFileSync(indexPath, code)
|
|
102
|
+
|
|
103
|
+
const pkgPath = findPackageJson(srcDir)
|
|
104
|
+
expect(isEntryPoint(indexPath, pkgPath!)).toBe(true)
|
|
105
|
+
|
|
106
|
+
// Simulate checking for @packageDocumentation
|
|
107
|
+
const parsed = parseTSDocComment(`/**
|
|
108
|
+
* This is the main entry point.
|
|
109
|
+
* @packageDocumentation
|
|
110
|
+
*/`)
|
|
111
|
+
expect(hasPackageDocumentation(parsed.docComment!)).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should fail when entry point lacks @packageDocumentation', () => {
|
|
115
|
+
createPackageJson('./src/index.ts')
|
|
116
|
+
const srcDir = createSourceDir()
|
|
117
|
+
const indexPath = path.join(srcDir, 'index.ts')
|
|
118
|
+
|
|
119
|
+
const code = `/**
|
|
120
|
+
* This is the main entry point.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
export function foo() {}`
|
|
124
|
+
|
|
125
|
+
fs.writeFileSync(indexPath, code)
|
|
126
|
+
|
|
127
|
+
const pkgPath = findPackageJson(srcDir)
|
|
128
|
+
expect(isEntryPoint(indexPath, pkgPath!)).toBe(true)
|
|
129
|
+
|
|
130
|
+
// Simulate checking for @packageDocumentation
|
|
131
|
+
const parsed = parseTSDocComment(`/**
|
|
132
|
+
* This is the main entry point.
|
|
133
|
+
*/`)
|
|
134
|
+
expect(hasPackageDocumentation(parsed.docComment!)).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should not require @packageDocumentation for non-entry points', () => {
|
|
138
|
+
createPackageJson('./src/index.ts')
|
|
139
|
+
const srcDir = createSourceDir()
|
|
140
|
+
|
|
141
|
+
const indexPath = path.join(srcDir, 'index.ts')
|
|
142
|
+
fs.writeFileSync(indexPath, '/** @packageDocumentation */ export {}')
|
|
143
|
+
|
|
144
|
+
const helperPath = path.join(srcDir, 'helper.ts')
|
|
145
|
+
fs.writeFileSync(helperPath, '// No package documentation needed')
|
|
146
|
+
|
|
147
|
+
const pkgPath = findPackageJson(srcDir)
|
|
148
|
+
// Helper is not an entry point, so no check needed
|
|
149
|
+
expect(isEntryPoint(helperPath, pkgPath!)).toBe(false)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as os from 'os'
|
|
5
|
+
import {
|
|
6
|
+
findApiExtractorConfig,
|
|
7
|
+
loadApiExtractorConfig,
|
|
8
|
+
getMessageLogLevel,
|
|
9
|
+
resolveConfig,
|
|
10
|
+
logLevelToSeverity,
|
|
11
|
+
clearConfigCache,
|
|
12
|
+
} from '../../src/utils/config-loader'
|
|
13
|
+
|
|
14
|
+
describe('config-loader', () => {
|
|
15
|
+
let tempDir: string
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'config-loader-test-'))
|
|
19
|
+
clearConfigCache()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
24
|
+
clearConfigCache()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('findApiExtractorConfig', () => {
|
|
28
|
+
it('should find api-extractor.json in the same directory', () => {
|
|
29
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
30
|
+
fs.writeFileSync(configPath, '{}')
|
|
31
|
+
|
|
32
|
+
const found = findApiExtractorConfig(tempDir)
|
|
33
|
+
expect(found).toBe(configPath)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should find api-extractor.json in parent directory', () => {
|
|
37
|
+
const subDir = path.join(tempDir, 'src')
|
|
38
|
+
fs.mkdirSync(subDir)
|
|
39
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
40
|
+
fs.writeFileSync(configPath, '{}')
|
|
41
|
+
|
|
42
|
+
const found = findApiExtractorConfig(subDir)
|
|
43
|
+
expect(found).toBe(configPath)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should return undefined when no config found', () => {
|
|
47
|
+
const found = findApiExtractorConfig(tempDir)
|
|
48
|
+
expect(found).toBeUndefined()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('loadApiExtractorConfig', () => {
|
|
53
|
+
it('should load and parse api-extractor.json', () => {
|
|
54
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
55
|
+
const config = {
|
|
56
|
+
mainEntryPointFilePath: './dist/index.d.ts',
|
|
57
|
+
messages: {
|
|
58
|
+
extractorMessageReporting: {
|
|
59
|
+
'ae-missing-release-tag': { logLevel: 'error' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(configPath, JSON.stringify(config))
|
|
64
|
+
|
|
65
|
+
const loaded = loadApiExtractorConfig(configPath)
|
|
66
|
+
expect(loaded).toEqual(config)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should handle JSON with comments', () => {
|
|
70
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
71
|
+
const content = `{
|
|
72
|
+
// This is a comment
|
|
73
|
+
"mainEntryPointFilePath": "./dist/index.d.ts"
|
|
74
|
+
/* Block comment */
|
|
75
|
+
}`
|
|
76
|
+
fs.writeFileSync(configPath, content)
|
|
77
|
+
|
|
78
|
+
const loaded = loadApiExtractorConfig(configPath)
|
|
79
|
+
expect(loaded?.mainEntryPointFilePath).toBe('./dist/index.d.ts')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should handle extends', () => {
|
|
83
|
+
const baseConfigPath = path.join(tempDir, 'base.json')
|
|
84
|
+
const baseConfig = {
|
|
85
|
+
messages: {
|
|
86
|
+
extractorMessageReporting: {
|
|
87
|
+
default: { logLevel: 'warning' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
fs.writeFileSync(baseConfigPath, JSON.stringify(baseConfig))
|
|
92
|
+
|
|
93
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
94
|
+
const config = {
|
|
95
|
+
extends: './base.json',
|
|
96
|
+
mainEntryPointFilePath: './dist/index.d.ts',
|
|
97
|
+
}
|
|
98
|
+
fs.writeFileSync(configPath, JSON.stringify(config))
|
|
99
|
+
|
|
100
|
+
const loaded = loadApiExtractorConfig(configPath)
|
|
101
|
+
expect(loaded?.mainEntryPointFilePath).toBe('./dist/index.d.ts')
|
|
102
|
+
expect(
|
|
103
|
+
loaded?.messages?.extractorMessageReporting?.default?.logLevel,
|
|
104
|
+
).toBe('warning')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should return null for invalid JSON', () => {
|
|
108
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
109
|
+
fs.writeFileSync(configPath, 'invalid json')
|
|
110
|
+
|
|
111
|
+
const loaded = loadApiExtractorConfig(configPath)
|
|
112
|
+
expect(loaded).toBeNull()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should return null for non-existent file', () => {
|
|
116
|
+
const configPath = path.join(tempDir, 'non-existent.json')
|
|
117
|
+
const loaded = loadApiExtractorConfig(configPath)
|
|
118
|
+
expect(loaded).toBeNull()
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('getMessageLogLevel', () => {
|
|
123
|
+
it('should return specific message log level', () => {
|
|
124
|
+
const config = {
|
|
125
|
+
messages: {
|
|
126
|
+
extractorMessageReporting: {
|
|
127
|
+
'ae-missing-release-tag': { logLevel: 'error' as const },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const level = getMessageLogLevel(config, 'ae-missing-release-tag')
|
|
133
|
+
expect(level).toBe('error')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should return default log level when message not configured', () => {
|
|
137
|
+
const config = {
|
|
138
|
+
messages: {
|
|
139
|
+
extractorMessageReporting: {
|
|
140
|
+
default: { logLevel: 'warning' as const },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const level = getMessageLogLevel(config, 'ae-missing-release-tag')
|
|
146
|
+
expect(level).toBe('warning')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should return warning for null config', () => {
|
|
150
|
+
const level = getMessageLogLevel(null, 'ae-missing-release-tag')
|
|
151
|
+
expect(level).toBe('warning')
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('logLevelToSeverity', () => {
|
|
156
|
+
it('should map error to 2', () => {
|
|
157
|
+
expect(logLevelToSeverity('error')).toBe(2)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should map warning to 1', () => {
|
|
161
|
+
expect(logLevelToSeverity('warning')).toBe(1)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should map none to 0', () => {
|
|
165
|
+
expect(logLevelToSeverity('none')).toBe(0)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('resolveConfig', () => {
|
|
170
|
+
it('should use explicit config path when provided', () => {
|
|
171
|
+
const configPath = path.join(tempDir, 'custom-config.json')
|
|
172
|
+
const config = { mainEntryPointFilePath: './custom.d.ts' }
|
|
173
|
+
fs.writeFileSync(configPath, JSON.stringify(config))
|
|
174
|
+
|
|
175
|
+
const filePath = path.join(tempDir, 'src', 'index.ts')
|
|
176
|
+
const resolved = resolveConfig(filePath, configPath)
|
|
177
|
+
expect(resolved?.mainEntryPointFilePath).toBe('./custom.d.ts')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should auto-discover config when no explicit path', () => {
|
|
181
|
+
const configPath = path.join(tempDir, 'api-extractor.json')
|
|
182
|
+
const config = { mainEntryPointFilePath: './dist/index.d.ts' }
|
|
183
|
+
fs.writeFileSync(configPath, JSON.stringify(config))
|
|
184
|
+
|
|
185
|
+
const srcDir = path.join(tempDir, 'src')
|
|
186
|
+
fs.mkdirSync(srcDir)
|
|
187
|
+
const filePath = path.join(srcDir, 'index.ts')
|
|
188
|
+
|
|
189
|
+
const resolved = resolveConfig(filePath)
|
|
190
|
+
expect(resolved?.mainEntryPointFilePath).toBe('./dist/index.d.ts')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should return null when no config found', () => {
|
|
194
|
+
const filePath = path.join(tempDir, 'index.ts')
|
|
195
|
+
const resolved = resolveConfig(filePath)
|
|
196
|
+
expect(resolved).toBeNull()
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
})
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as os from 'os'
|
|
5
|
+
import {
|
|
6
|
+
findPackageJson,
|
|
7
|
+
loadPackageJson,
|
|
8
|
+
resolveEntryPoints,
|
|
9
|
+
isEntryPoint,
|
|
10
|
+
clearPackageJsonCache,
|
|
11
|
+
} from '../../src/utils/entry-point'
|
|
12
|
+
|
|
13
|
+
describe('entry-point', () => {
|
|
14
|
+
let tempDir: string
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'entry-point-test-'))
|
|
18
|
+
clearPackageJsonCache()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
23
|
+
clearPackageJsonCache()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('findPackageJson', () => {
|
|
27
|
+
it('should find package.json in the same directory', () => {
|
|
28
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
29
|
+
fs.writeFileSync(pkgPath, '{}')
|
|
30
|
+
|
|
31
|
+
const found = findPackageJson(tempDir)
|
|
32
|
+
expect(found).toBe(pkgPath)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should find package.json in parent directory', () => {
|
|
36
|
+
const subDir = path.join(tempDir, 'src')
|
|
37
|
+
fs.mkdirSync(subDir)
|
|
38
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
39
|
+
fs.writeFileSync(pkgPath, '{}')
|
|
40
|
+
|
|
41
|
+
const found = findPackageJson(subDir)
|
|
42
|
+
expect(found).toBe(pkgPath)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should return undefined when no package.json found', () => {
|
|
46
|
+
const found = findPackageJson(tempDir)
|
|
47
|
+
expect(found).toBeUndefined()
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('loadPackageJson', () => {
|
|
52
|
+
it('should load and parse package.json', () => {
|
|
53
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
54
|
+
const pkg = { name: 'test-package', main: './dist/index.js' }
|
|
55
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg))
|
|
56
|
+
|
|
57
|
+
const loaded = loadPackageJson(pkgPath)
|
|
58
|
+
expect(loaded).toEqual(pkg)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should return null for invalid JSON', () => {
|
|
62
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
63
|
+
fs.writeFileSync(pkgPath, 'invalid json')
|
|
64
|
+
|
|
65
|
+
const loaded = loadPackageJson(pkgPath)
|
|
66
|
+
expect(loaded).toBeNull()
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('resolveEntryPoints', () => {
|
|
71
|
+
it('should resolve main entry point', () => {
|
|
72
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
73
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ main: './dist/index.js' }))
|
|
74
|
+
|
|
75
|
+
const entryPoints = resolveEntryPoints(pkgPath)
|
|
76
|
+
expect(entryPoints.main).toBe(path.join(tempDir, 'dist/index.js'))
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should resolve types entry point', () => {
|
|
80
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
81
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ types: './dist/index.d.ts' }))
|
|
82
|
+
|
|
83
|
+
const entryPoints = resolveEntryPoints(pkgPath)
|
|
84
|
+
expect(entryPoints.types).toBe(path.join(tempDir, 'dist/index.d.ts'))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should resolve typings entry point', () => {
|
|
88
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
89
|
+
fs.writeFileSync(
|
|
90
|
+
pkgPath,
|
|
91
|
+
JSON.stringify({ typings: './dist/index.d.ts' }),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const entryPoints = resolveEntryPoints(pkgPath)
|
|
95
|
+
expect(entryPoints.types).toBe(path.join(tempDir, 'dist/index.d.ts'))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should resolve simple exports', () => {
|
|
99
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
100
|
+
fs.writeFileSync(
|
|
101
|
+
pkgPath,
|
|
102
|
+
JSON.stringify({
|
|
103
|
+
exports: {
|
|
104
|
+
'.': './dist/index.js',
|
|
105
|
+
'./utils': './dist/utils.js',
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const entryPoints = resolveEntryPoints(pkgPath)
|
|
111
|
+
expect(entryPoints.exports).toContain(path.join(tempDir, 'dist/index.js'))
|
|
112
|
+
expect(entryPoints.exports).toContain(path.join(tempDir, 'dist/utils.js'))
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should resolve nested exports', () => {
|
|
116
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
117
|
+
fs.writeFileSync(
|
|
118
|
+
pkgPath,
|
|
119
|
+
JSON.stringify({
|
|
120
|
+
exports: {
|
|
121
|
+
'.': {
|
|
122
|
+
import: './dist/index.mjs',
|
|
123
|
+
require: './dist/index.cjs',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const entryPoints = resolveEntryPoints(pkgPath)
|
|
130
|
+
expect(entryPoints.exports).toContain(
|
|
131
|
+
path.join(tempDir, 'dist/index.mjs'),
|
|
132
|
+
)
|
|
133
|
+
expect(entryPoints.exports).toContain(
|
|
134
|
+
path.join(tempDir, 'dist/index.cjs'),
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('isEntryPoint', () => {
|
|
140
|
+
it('should identify main entry point', () => {
|
|
141
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
142
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ main: './dist/index.js' }))
|
|
143
|
+
|
|
144
|
+
const filePath = path.join(tempDir, 'dist/index.js')
|
|
145
|
+
expect(isEntryPoint(filePath, pkgPath)).toBe(true)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should identify source file when main points to source', () => {
|
|
149
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
150
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ main: './src/index.ts' }))
|
|
151
|
+
|
|
152
|
+
const filePath = path.join(tempDir, 'src/index.ts')
|
|
153
|
+
expect(isEntryPoint(filePath, pkgPath)).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should not identify non-entry point files', () => {
|
|
157
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
158
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ main: './dist/index.js' }))
|
|
159
|
+
|
|
160
|
+
const filePath = path.join(tempDir, 'src/utils.ts')
|
|
161
|
+
expect(isEntryPoint(filePath, pkgPath)).toBe(false)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should handle relative paths in package.json', () => {
|
|
165
|
+
const pkgPath = path.join(tempDir, 'package.json')
|
|
166
|
+
fs.writeFileSync(pkgPath, JSON.stringify({ main: 'dist/index.js' }))
|
|
167
|
+
|
|
168
|
+
const filePath = path.join(tempDir, 'dist/index.js')
|
|
169
|
+
expect(isEntryPoint(filePath, pkgPath)).toBe(true)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
parseTSDocComment,
|
|
4
|
+
extractReleaseTag,
|
|
5
|
+
hasOverrideTag,
|
|
6
|
+
hasPackageDocumentation,
|
|
7
|
+
} from '../../src/utils/tsdoc-parser'
|
|
8
|
+
|
|
9
|
+
describe('tsdoc-parser', () => {
|
|
10
|
+
describe('parseTSDocComment', () => {
|
|
11
|
+
it('should parse a simple TSDoc comment', () => {
|
|
12
|
+
const comment = `/**
|
|
13
|
+
* A simple comment.
|
|
14
|
+
*/`
|
|
15
|
+
const result = parseTSDocComment(comment)
|
|
16
|
+
expect(result.docComment).toBeDefined()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should handle comments with modifier tags', () => {
|
|
20
|
+
const comment = `/**
|
|
21
|
+
* A public API.
|
|
22
|
+
* @public
|
|
23
|
+
*/`
|
|
24
|
+
const result = parseTSDocComment(comment)
|
|
25
|
+
expect(result.docComment).toBeDefined()
|
|
26
|
+
expect(result.docComment?.modifierTagSet.isPublic()).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('extractReleaseTag', () => {
|
|
31
|
+
it('should extract @public tag', () => {
|
|
32
|
+
const comment = `/**
|
|
33
|
+
* @public
|
|
34
|
+
*/`
|
|
35
|
+
const result = parseTSDocComment(comment)
|
|
36
|
+
const tag = extractReleaseTag(result.docComment!)
|
|
37
|
+
expect(tag).toBe('public')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should extract @beta tag', () => {
|
|
41
|
+
const comment = `/**
|
|
42
|
+
* @beta
|
|
43
|
+
*/`
|
|
44
|
+
const result = parseTSDocComment(comment)
|
|
45
|
+
const tag = extractReleaseTag(result.docComment!)
|
|
46
|
+
expect(tag).toBe('beta')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should extract @alpha tag', () => {
|
|
50
|
+
const comment = `/**
|
|
51
|
+
* @alpha
|
|
52
|
+
*/`
|
|
53
|
+
const result = parseTSDocComment(comment)
|
|
54
|
+
const tag = extractReleaseTag(result.docComment!)
|
|
55
|
+
expect(tag).toBe('alpha')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should extract @internal tag', () => {
|
|
59
|
+
const comment = `/**
|
|
60
|
+
* @internal
|
|
61
|
+
*/`
|
|
62
|
+
const result = parseTSDocComment(comment)
|
|
63
|
+
const tag = extractReleaseTag(result.docComment!)
|
|
64
|
+
expect(tag).toBe('internal')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should return undefined when no release tag', () => {
|
|
68
|
+
const comment = `/**
|
|
69
|
+
* Just a description.
|
|
70
|
+
*/`
|
|
71
|
+
const result = parseTSDocComment(comment)
|
|
72
|
+
const tag = extractReleaseTag(result.docComment!)
|
|
73
|
+
expect(tag).toBeUndefined()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('hasOverrideTag', () => {
|
|
78
|
+
it('should detect @override tag', () => {
|
|
79
|
+
const comment = `/**
|
|
80
|
+
* @override
|
|
81
|
+
*/`
|
|
82
|
+
const result = parseTSDocComment(comment)
|
|
83
|
+
expect(hasOverrideTag(result.docComment!)).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should return false when no @override tag', () => {
|
|
87
|
+
const comment = `/**
|
|
88
|
+
* Just a comment.
|
|
89
|
+
*/`
|
|
90
|
+
const result = parseTSDocComment(comment)
|
|
91
|
+
expect(hasOverrideTag(result.docComment!)).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('hasPackageDocumentation', () => {
|
|
96
|
+
it('should detect @packageDocumentation tag', () => {
|
|
97
|
+
const comment = `/**
|
|
98
|
+
* Package description.
|
|
99
|
+
* @packageDocumentation
|
|
100
|
+
*/`
|
|
101
|
+
const result = parseTSDocComment(comment)
|
|
102
|
+
expect(hasPackageDocumentation(result.docComment!)).toBe(true)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should return false when no @packageDocumentation tag', () => {
|
|
106
|
+
const comment = `/**
|
|
107
|
+
* Just a comment.
|
|
108
|
+
*/`
|
|
109
|
+
const result = parseTSDocComment(comment)
|
|
110
|
+
expect(hasPackageDocumentation(result.docComment!)).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
resolve: {
|
|
6
|
+
alias: {
|
|
7
|
+
'@': path.resolve(__dirname, 'src/index.ts'),
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
test: {
|
|
11
|
+
testTimeout: 30000,
|
|
12
|
+
server: {
|
|
13
|
+
deps: {
|
|
14
|
+
inline: ['fast-glob'],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
coverage: {
|
|
18
|
+
provider: 'v8',
|
|
19
|
+
reporter: ['text', 'json', 'html'],
|
|
20
|
+
reportsDirectory: './coverage',
|
|
21
|
+
include: ['src/**/*.ts'],
|
|
22
|
+
exclude: ['src/**/*.d.ts', 'src/**/index.ts'],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
})
|