diff --git a/packages/table-core/package.json b/packages/table-core/package.json index 77fae4de54..14a2c09c1d 100644 --- a/packages/table-core/package.json +++ b/packages/table-core/package.json @@ -70,7 +70,7 @@ "test:lib:dev": "pnpm test:lib --watch", "test:types": "tsc && tsc -p tests/tsconfig.declaration-emit.json", "test:build": "publint --strict", - "build": "tsdown" + "build": "tsdown && node ../../scripts/rewrite-table-core-dts.mjs" }, "dependencies": { "@tanstack/store": "^0.11.0" diff --git a/packages/table-core/src/core/headers/coreHeadersFeature.types.ts b/packages/table-core/src/core/headers/coreHeadersFeature.types.ts index d1d9acf69d..ee683d3c7c 100644 --- a/packages/table-core/src/core/headers/coreHeadersFeature.types.ts +++ b/packages/table-core/src/core/headers/coreHeadersFeature.types.ts @@ -1,6 +1,6 @@ import type { CellData, RowData } from '../../types/type-utils' import type { TableFeatures } from '../../types/TableFeatures' -import type { Table } from '../../types/Table' +import type { Table, Table_Internal } from '../../types/Table' import type { Header } from '../../types/Header' import type { HeaderGroup } from '../../types/HeaderGroup' import type { Column } from '../../types/Column' @@ -96,7 +96,7 @@ export interface Header_CoreProperties< /** * Reference to the parent table instance. */ - table: Table + table: Table_Internal } export interface Header_Header< diff --git a/packages/table-core/src/types/Column.ts b/packages/table-core/src/types/Column.ts index 59d12ad91d..28b5866609 100644 --- a/packages/table-core/src/types/Column.ts +++ b/packages/table-core/src/types/Column.ts @@ -1,3 +1,4 @@ +import type { Table_Internal } from './Table' import type { Column_RowSorting } from '../features/row-sorting/rowSortingFeature.types' import type { Column_ColumnFaceting } from '../features/column-faceting/columnFacetingFeature.types' import type { Column_ColumnFiltering } from '../features/column-filtering/columnFilteringFeature.types' @@ -46,6 +47,7 @@ export interface Column_Internal< in out TFeatures extends TableFeatures, in out TData extends RowData, TValue = unknown, -> extends Omit, 'columnDef'> { +> extends Omit, 'columnDef' | 'table'> { columnDef: ColumnDefBase_All + table: Table_Internal } diff --git a/scripts/rewrite-table-core-dts.mjs b/scripts/rewrite-table-core-dts.mjs new file mode 100644 index 0000000000..551268ab64 --- /dev/null +++ b/scripts/rewrite-table-core-dts.mjs @@ -0,0 +1,191 @@ +import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const packageRoot = fileURLToPath( + new URL('../packages/table-core/', import.meta.url), +) +const distDir = join(packageRoot, 'dist') +const forbiddenTypeNames = ['Table_Internal', 'Column_Internal'] + +function walkDeclarationFiles(dir) { + const files = [] + + for (const entry of readdirSync(dir)) { + const path = join(dir, entry) + const stat = statSync(path) + + if (stat.isDirectory()) { + files.push(...walkDeclarationFiles(path)) + continue + } + + if (path.endsWith('.d.ts') || path.endsWith('.d.cts')) { + files.push(path) + } + } + + return files +} + +function removeExportedInterface(source, interfaceName) { + let start = source.indexOf(`export interface ${interfaceName}`) + + if (start === -1) { + start = source.indexOf(`interface ${interfaceName}`) + } + + if (start === -1) { + return source + } + + const bodyStart = source.indexOf('{', start) + + if (bodyStart === -1) { + throw new Error(`Could not find body for ${interfaceName}`) + } + + let depth = 0 + + for (let i = bodyStart; i < source.length; i++) { + const char = source[i] + + if (char === '{') { + depth++ + } else if (char === '}') { + depth-- + + if (depth === 0) { + let end = i + 1 + + while (source[end] === '\n' || source[end] === '\r') { + end++ + } + + return source.slice(0, start) + source.slice(end) + } + } + } + + throw new Error(`Could not find end of ${interfaceName}`) +} + +function removeTypeAlias(source, typeName) { + const aliasPattern = new RegExp( + String.raw`\b(?:export\s+)?type\s+${typeName}\b`, + ) + const match = aliasPattern.exec(source) + + if (!match) { + return source + } + + const start = match.index + const end = source.indexOf(';', start) + + if (end === -1) { + throw new Error(`Could not find end of ${typeName}`) + } + + return source.slice(0, start) + source.slice(end + 1) +} + +function getSpecifierName(specifier) { + return specifier + .replace(/^type\s+/, '') + .split(/\s+as\s+/)[0] + ?.trim() +} + +function removeNamedSpecifiers(source, names) { + return source + .replace( + /\b(import|export)(\s+type)?\s+\{([^}]+)\}\s+from\s+([^;\n]+);/g, + (statement, kind, typeKeyword = '', specifiers, fromClause) => { + const nextSpecifiers = specifiers + .split(',') + .map((specifier) => specifier.trim()) + .filter(Boolean) + .filter((specifier) => { + const importedName = getSpecifierName(specifier) + return importedName && !names.includes(importedName) + }) + + if (!nextSpecifiers.length) { + return '' + } + + return `${kind}${typeKeyword} { ${nextSpecifiers.join( + ', ', + )} } from ${fromClause};` + }, + ) + .replace( + /\bexport(\s+type)?\s+\{([^}]+)\};/g, + (statement, typeKeyword = '', specifiers) => { + const nextSpecifiers = specifiers + .split(',') + .map((specifier) => specifier.trim()) + .filter(Boolean) + .filter((specifier) => { + const exportedName = getSpecifierName(specifier) + return exportedName && !names.includes(exportedName) + }) + + if (!nextSpecifiers.length) { + return '' + } + + return `export${typeKeyword} { ${nextSpecifiers.join(', ')} };` + }, + ) +} + +function rewriteDeclaration(source) { + let next = source + + for (const typeName of forbiddenTypeNames) { + next = removeExportedInterface(next, typeName) + } + + next = removeTypeAlias(next, 'Table_InternalBroadenedKeys') + next = removeNamedSpecifiers(next, forbiddenTypeNames) + + next = next.replaceAll('Table_Internal<', 'Table<') + next = next.replaceAll('Column_Internal<', 'Column<') + next = next.replaceAll('Table_Internal', 'Table') + next = next.replaceAll('Column_Internal', 'Column') + + return next +} + +const files = walkDeclarationFiles(distDir) + +for (const file of files) { + const source = readFileSync(file, 'utf8') + const next = rewriteDeclaration(source) + + if (next !== source) { + writeFileSync(file, next) + } +} + +const leaks = [] + +for (const file of files) { + const source = readFileSync(file, 'utf8') + + for (const typeName of forbiddenTypeNames) { + if (source.includes(typeName)) { + leaks.push(`${file}: ${typeName}`) + } + } +} + +if (leaks.length) { + throw new Error( + `Internal table-core types leaked into emitted declarations:\n${leaks.join( + '\n', + )}`, + ) +}