fix(asr): validate generated editor declarations semantically

This commit is contained in:
Harvey Zhao committed 2026-09-13 16:47:51 +08:00
1 parent c723c70251
commit 1117107032
4 files changed
+97 -23

No files matched your search

+47 -22
View File
@@ -1,26 +1,51 @@
interface AudioChunk {
pcm: ArrayBuffer
wav: ArrayBuffer
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAsrDefinitions {
export interface AudioChunk {
pcm: ArrayBuffer
wav: ArrayBuffer
}
export interface AsrPluginOption {
length?: number
interval?: number
sampleRate?: number
autoHideTimeout?: number
onAudioChunk?: (chunk: AudioChunk) => void | Promise<void>
}
export interface AsrPluginInstance {
name: 'artplayerPluginAsr'
stop: () => void
hide: () => void
append: (subtitle: string) => void
}
/** Historical factory shape, including void stop and callback results. */
export type Factory = (option?: AsrPluginOption) => (art: Artplayer) => AsrPluginInstance
/** Accurate asynchronous view available through the /runtime entry. */
export interface RuntimeOption extends Omit<AsrPluginOption, 'onAudioChunk'> {
/** Capture the media stream without taking ownership of its playback route. */
audioInput?: {
type: 'capture'
}
onAudioChunk?: (chunk: AudioChunk) => string | void | null | Promise<string | void | null>
}
export interface RuntimeResult extends Omit<AsrPluginInstance, 'stop'> {
stop: () => Promise<void>
}
export interface RuntimeFactory {
(option?: RuntimeOption): (art: Artplayer) => RuntimeResult
readonly default: RuntimeFactory
}
export function artplayerPluginAsr(option?: AsrPluginOption): (art: Artplayer) => AsrPluginInstance
}
interface AsrPluginOption {
length?: number
interval?: number
sampleRate?: number
autoHideTimeout?: number
onAudioChunk?: (chunk: AudioChunk) => void | Promise<void>
declare const artplayerPluginAsr: typeof artplayerPluginAsrDefinitions.artplayerPluginAsr
declare namespace artplayerPluginAsr {
export type AudioChunk = artplayerPluginAsrDefinitions.AudioChunk
export type AsrPluginOption = artplayerPluginAsrDefinitions.AsrPluginOption
export type AsrPluginInstance = artplayerPluginAsrDefinitions.AsrPluginInstance
export type Factory = artplayerPluginAsrDefinitions.Factory
export type RuntimeOption = artplayerPluginAsrDefinitions.RuntimeOption
export type RuntimeResult = artplayerPluginAsrDefinitions.RuntimeResult
export type RuntimeFactory = artplayerPluginAsrDefinitions.RuntimeFactory
}
interface AsrPluginInstance {
name: 'artplayerPluginAsr'
stop: () => void
hide: () => void
append: (subtitle: string) => void
}
declare function artplayerPluginAsr(option?: AsrPluginOption): (art: Artplayer) => AsrPluginInstance
export default artplayerPluginAsr
export = artplayerPluginAsr
export as namespace artplayerPluginAsr;
@@ -0,0 +1,19 @@
# PKG-ASR-05 编辑器生成修复检查点
完整执行 `yarn build:ts` 时发现 ASR 尚走旧文本拼接路径。新增的公开命名类型和
原有 `export default` 被直接保留,再拼接 `export =`,在严格独立编辑器编译中
产生 TS2309。此问题已修复,不只登记后推迟处理。
ASR 现在使用现有语义生成器:公共类型归入全局命名空间,默认工厂仍保持历史
同步声明,准确 RuntimeOption/RuntimeResult/RuntimeFactory 作为独立类型可用。
运行时源码、npm 根声明及播放器行为没有改变。
`test/editor-types.test.js` 重建旧文本生成结果并断言 TS2309;对新声明验证普通
工厂替换、历史 void stop、精确异步 stop 与显式 capture 输入。TS 5.9.3 和 4.3.5
均在 strict、skipLibCheck=false 下通过,并各拒绝三个指定反例。生成产物与
格式化结果逐字核对。两项编辑器测试通过,日志为
`refactor/.cache/asr-editor-tests.log`,生成日志为
`refactor/.cache/asr-editor-generation.log`。
此修复作为 ASR-05 的独立本地检查点提交;ASR-05 的真实设备等剩余验证仍未完成。
未将静态声明测试描述为新的浏览器播放证据,也未推送或发布。
+1 -1
View File
@@ -53,7 +53,7 @@ console.log(`✨ Built ${artplayerTSoutput}`);
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
const semanticPlugin = ['artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const semanticPlugin = ['artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const localTypes = name === 'artplayerProxyMediabunny' ? { './media': fs.readFileSync(path.join(path.dirname(type), 'media.d.ts'), 'utf8') } : {}
const code = semanticPlugin
? generatePluginEditorDeclaration(source, name, localTypes)
+30
View File
@@ -5,6 +5,7 @@ import { test } from 'node:test'
import { ESLint } from 'eslint'
import compat from 'typescript-compat'
import { asGlobalDeclaration, checkCoreEditorDeclaration, generateCoreEditorDeclaration } from '../scripts/editor-types.mjs'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../scripts/plugin-editor-types.mjs'
test('editor declarations are reproducible, standalone and preserve constructor/named types in both compilers', async () => {
const generated = generateCoreEditorDeclaration()
@@ -19,3 +20,32 @@ test('editor declarations are reproducible, standalone and preserve constructor/
assert(checkCoreEditorDeclaration(corrupted).some(diagnostic => diagnostic.code === 2304), 'Invalid output must not be hidden by skipLibCheck')
assert.throws(() => asGlobalDeclaration('export { default } from "./missing"', 'Artplayer'), /unresolved exports/)
})
test('ASR editor generation supports named types without mixing default and export assignment', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts', 'utf8')
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const old = `${source.replace(/^import.*$/gim, '')}\nexport = artplayerPluginAsr;\nexport as namespace artplayerPluginAsr;\n`
assert(checkPluginEditorDeclaration(old, core).some(item => item.code === 2309), 'The old textual generator must reproduce its conflicting export assignment')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAsr')
const file = 'docs/assets/ts/artplayer-plugin-asr.d.ts'
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
assert.equal(formatted.errorCount, 0)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
const consumer = `declare const art: Artplayer;
const factory: typeof artplayerPluginAsr = () => () => ({ name: 'artplayerPluginAsr', stop() {}, hide() {}, append(_text: string) {} });
const oldResult: void = factory()(art).stop();
const option: artplayerPluginAsr.RuntimeOption = { audioInput: { type: 'capture' }, onAudioChunk: async () => 'subtitle' };
declare const runtime: artplayerPluginAsr.RuntimeFactory;
const stopped: Promise<void> = runtime(option)(art).stop();
// @ts-expect-error Root stop retains historical void type.
const rootPromise: Promise<void> = artplayerPluginAsr()(art).stop();
// @ts-expect-error Capture input is runtime-only.
artplayerPluginAsr({ audioInput: { type: 'capture' } });
// @ts-expect-error Unknown capture mode.
const invalid: artplayerPluginAsr.RuntimeOption = { audioInput: { type: 'direct' } };
void [oldResult, stopped, rootPromise, invalid];`
for (const compiler of [undefined, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
}
})