- 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
68 lines
2.8 KiB
TypeScript
68 lines
2.8 KiB
TypeScript
// 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')
|
|
})
|