fix: export JournalImageVariant from one auto-imported file
- types.ts re-exported the interface srcset.ts declares, so every build of an app with the layer warned Duplicated imports "JournalImageVariant" - a test checks that every name of app/utils and app/composables has one home
This commit is contained in:
@@ -4,8 +4,6 @@
|
||||
|
||||
import type { JournalImageVariant } from './srcset.ts'
|
||||
|
||||
export type { JournalImageVariant }
|
||||
|
||||
/** The author object embedded in a post row. `null` when the post has none. */
|
||||
export interface JournalAuthorRef {
|
||||
id: number
|
||||
|
||||
67
tests/autoImports.test.ts
Normal file
67
tests/autoImports.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// Nuxt auto-imports every export of app/utils and app/composables. A name
|
||||
// exported from two of those files is a "Duplicated imports" warning on
|
||||
// every build of an app with the layer (an interface re-exported next to
|
||||
// its declaration counts), so each name has one home.
|
||||
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readdirSync, readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const root = new URL('..', import.meta.url).pathname
|
||||
|
||||
/** The names a module exports: declarations and export lists. */
|
||||
function exportedNames(source: string): string[] {
|
||||
const names: string[] = []
|
||||
const decl = /^export\s+(?:declare\s+)?(?:async\s+)?(?:interface|type|function\*?|const|let|var|class|enum)\s+([A-Za-z_$][\w$]*)/gm
|
||||
for (const m of source.matchAll(decl)) names.push(m[1]!)
|
||||
const list = /^export\s+(?:type\s+)?\{([^}]*)\}/gm
|
||||
for (const m of source.matchAll(list)) {
|
||||
for (const part of m[1]!.split(',')) {
|
||||
const name = part.trim().split(/\s+as\s+/).pop()?.replace(/^type\s+/, '').trim()
|
||||
if (name) names.push(name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
function autoImportedFiles(): string[] {
|
||||
const out: string[] = []
|
||||
for (const dir of ['app/utils', 'app/composables']) {
|
||||
for (const f of readdirSync(join(root, dir))) {
|
||||
if (/\.(ts|mts|js|mjs)$/.test(f) && !f.endsWith('.d.ts')) out.push(join(dir, f))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
test('exportedNames reads declarations and export lists', () => {
|
||||
assert.deepEqual(
|
||||
exportedNames("export interface A {}\nexport type B = 1\nexport function c() {}\nexport const d = 1\nexport type { E }\nexport { f as g, type H }\n"),
|
||||
['A', 'B', 'c', 'd', 'E', 'g', 'H'],
|
||||
)
|
||||
})
|
||||
|
||||
test('a name re-exported next to its declaration is a duplicate', () => {
|
||||
const files = {
|
||||
'app/utils/srcset.ts': 'export interface JournalImageVariant { width: number }',
|
||||
'app/utils/types.ts': "import type { JournalImageVariant } from './srcset.ts'\nexport type { JournalImageVariant }",
|
||||
}
|
||||
const seen = new Map<string, string[]>()
|
||||
for (const [file, src] of Object.entries(files)) {
|
||||
for (const name of exportedNames(src)) seen.set(name, [...(seen.get(name) ?? []), file])
|
||||
}
|
||||
assert.deepEqual(seen.get('JournalImageVariant'), ['app/utils/srcset.ts', 'app/utils/types.ts'])
|
||||
})
|
||||
|
||||
test('every auto-imported name is exported from one file only', () => {
|
||||
const homes = new Map<string, string[]>()
|
||||
for (const file of autoImportedFiles()) {
|
||||
for (const name of exportedNames(readFileSync(join(root, file), 'utf8'))) {
|
||||
homes.set(name, [...(homes.get(name) ?? []), file])
|
||||
}
|
||||
}
|
||||
const duplicates = [...homes].filter(([, files]) => files.length > 1).map(([name, files]) => `${name}: ${files.join(', ')}`)
|
||||
assert.deepEqual(duplicates, [])
|
||||
assert.ok(homes.has('JournalImageVariant'), 'JournalImageVariant is still auto-imported')
|
||||
})
|
||||
Reference in New Issue
Block a user