From d66379adbe6d890037dd5eadc436c2364733b14d Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 00:41:55 +0100 Subject: [PATCH 01/10] fix: filter out test modules and comments --- src/node/_tasks/dts/_declareModuleFix.ts | 24 +++++++----------------- test/_extractModuleBlocks.test.ts | 10 ++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/node/_tasks/dts/_declareModuleFix.ts b/src/node/_tasks/dts/_declareModuleFix.ts index 76bd3efa..be6795da 100644 --- a/src/node/_tasks/dts/_declareModuleFix.ts +++ b/src/node/_tasks/dts/_declareModuleFix.ts @@ -1,5 +1,6 @@ import fs from 'fs/promises' import path from 'path' +import globby from 'globby' /** * '@microsoft/api-extractor' omits declare module blocks @@ -22,9 +23,12 @@ export async function _appendModuleBlocks({ async function _extractModuleBlocksFromTypes(dirname: string): Promise { const moduleBlocks: string[] = [] - const files = await _getFileList(dirname) + const files = await globby(path.resolve(dirname, '**/*.d.ts')) for (const fileName of files) { + if (fileName.includes('.test.')) { + continue + } const content = await fs.readFile(path.resolve(dirname, fileName), { encoding: 'utf-8', }) @@ -37,21 +41,6 @@ async function _extractModuleBlocksFromTypes(dirname: string): Promise return moduleBlocks } -async function _getFileList(dirName: string): Promise { - let files: string[] = [] - const filesInCurrentDir = await fs.readdir(dirName, {withFileTypes: true}) - - for (const fileInDir of filesInCurrentDir) { - if (fileInDir.isDirectory()) { - files = [...files, ...(await _getFileList(path.resolve(dirName, fileInDir.name)))] - } else { - files.push(path.resolve(dirName, fileInDir.name)) - } - } - - return files -} - export function _extractModuleBlocks(fileContent: string): string[] { const moduleBlocks: string[] = [] @@ -62,8 +51,9 @@ export function _extractModuleBlocks(fileContent: string): string[] { for (const line of lines) { if (!moduleLines.length) { + const isModuleDeclaration = line.match(/^\s*declare module.+$/)?.length const moduleIndex = line.indexOf('declare module') - const isModuleStart = moduleIndex > -1 + const isModuleStart = isModuleDeclaration && moduleIndex > -1 if (isModuleStart) { moduleIndentLevel = moduleIndex diff --git a/test/_extractModuleBlocks.test.ts b/test/_extractModuleBlocks.test.ts index 99347cb4..827b259c 100644 --- a/test/_extractModuleBlocks.test.ts +++ b/test/_extractModuleBlocks.test.ts @@ -18,9 +18,19 @@ test('extract module block', () => { x: string } } + + /** + * declare module 'sanity' { + * export interface StringOptions { + * myCustomOption?: boolean + * } + * } + * / ` ) + expect(blocks.length).toEqual(2) + expect(blocks[0]).toEqual(outdent` declare module X { interface A { From eb56adfa6fcff3e7a82b692358afa62b1719c847 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 00:47:22 +0100 Subject: [PATCH 02/10] chore: lintfix --- src/node/_tasks/dts/_declareModuleFix.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/_tasks/dts/_declareModuleFix.ts b/src/node/_tasks/dts/_declareModuleFix.ts index be6795da..e1c61cc5 100644 --- a/src/node/_tasks/dts/_declareModuleFix.ts +++ b/src/node/_tasks/dts/_declareModuleFix.ts @@ -29,6 +29,7 @@ async function _extractModuleBlocksFromTypes(dirname: string): Promise if (fileName.includes('.test.')) { continue } + const content = await fs.readFile(path.resolve(dirname, fileName), { encoding: 'utf-8', }) From 9fb740571682dd7ec408a6adb9c1e4016673b11d Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 11:32:34 +0100 Subject: [PATCH 03/10] fix: use extractor source files to inform where to extract modules --- playground/ts/src/index.ts | 1 + playground/ts/src/module1.ts | 37 +++++++++++++++++++ playground/ts/src/module2.ts | 6 +++ src/node/_tasks/dts/_declareModuleFix.ts | 47 +++++++++++++++++------- src/node/_tasks/dts/_doExtract.ts | 5 ++- test/__snapshots__/cli.test.ts.snap | 27 ++++++++++++++ test/_extractModuleBlocks.test.ts | 8 ++++ test/cli.test.ts | 14 +++++++ 8 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 playground/ts/src/module1.ts create mode 100644 playground/ts/src/module2.ts diff --git a/playground/ts/src/index.ts b/playground/ts/src/index.ts index 947ccfa0..5a097cbc 100644 --- a/playground/ts/src/index.ts +++ b/playground/ts/src/index.ts @@ -1,2 +1,3 @@ /** @public */ export const VERSION = '1.0.0' +export * from './module1' diff --git a/playground/ts/src/module1.ts b/playground/ts/src/module1.ts new file mode 100644 index 00000000..7834b0a6 --- /dev/null +++ b/playground/ts/src/module1.ts @@ -0,0 +1,37 @@ +export * from './module2' + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export interface _Dummy { + field: string +} + +declare module './module2' { + export interface IncludedModuleDummy { + addedField: string + } +} + +/** + * declare namespace ExcludedModule { + * interface TSDocsDummy { + * field: string + * } + * } + */ + +/* + declare namespace ExcludedModule { + interface BlockCommentDummy { + field: string + } + } + */ + +// declare namespace ExcludedModule { +// interface CommentDummy { +// field: string +// } +// } diff --git a/playground/ts/src/module2.ts b/playground/ts/src/module2.ts new file mode 100644 index 00000000..4750052a --- /dev/null +++ b/playground/ts/src/module2.ts @@ -0,0 +1,6 @@ +/** + * @public + */ +export interface IncludedModuleDummy { + field: string +} diff --git a/src/node/_tasks/dts/_declareModuleFix.ts b/src/node/_tasks/dts/_declareModuleFix.ts index e1c61cc5..157ff188 100644 --- a/src/node/_tasks/dts/_declareModuleFix.ts +++ b/src/node/_tasks/dts/_declareModuleFix.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises' -import path from 'path' -import globby from 'globby' +import {ExtractorResult} from '@microsoft/api-extractor' +import {Program} from 'typescript' /** * '@microsoft/api-extractor' omits declare module blocks @@ -8,34 +8,40 @@ import globby from 'globby' */ export async function _appendModuleBlocks({ tsOutDir, + extractResult, extractTypesOutFile, }: { tsOutDir: string + extractResult: ExtractorResult extractTypesOutFile: string }): Promise { - const moduleBlocks = await _extractModuleBlocksFromTypes(tsOutDir) + const moduleBlocks = await _extractModuleBlocksFromTypes({tsOutDir, extractResult}) if (moduleBlocks.length) { await fs.appendFile(extractTypesOutFile, '\n' + moduleBlocks.join('\n\n')) } } -async function _extractModuleBlocksFromTypes(dirname: string): Promise { +async function _extractModuleBlocksFromTypes({ + tsOutDir, + extractResult, +}: { + tsOutDir: string + extractResult: ExtractorResult +}): Promise { const moduleBlocks: string[] = [] - const files = await globby(path.resolve(dirname, '**/*.d.ts')) + const program = extractResult.compilerState.program as Program - for (const fileName of files) { - if (fileName.includes('.test.')) { - continue - } + // all program files, including node_modules + const allProgramFiles = [...program.getSourceFiles()] - const content = await fs.readFile(path.resolve(dirname, fileName), { - encoding: 'utf-8', - }) + // just our compiled files used in the program + const sourceFiles = allProgramFiles.filter((sourceFile) => sourceFile.fileName.includes(tsOutDir)) - if (content.includes('declare module')) { - moduleBlocks.push(..._extractModuleBlocks(content)) + for (const sourceFile of sourceFiles) { + if (sourceFile.text.includes('declare module')) { + moduleBlocks.push(..._extractModuleBlocks(sourceFile.text)) } } @@ -46,11 +52,24 @@ export function _extractModuleBlocks(fileContent: string): string[] { const moduleBlocks: string[] = [] let moduleIndentLevel = 0 + let insideComment = false let moduleLines: string[] = [] const lines = fileContent.split('\n') for (const line of lines) { + // Note: Extractor already removes comment blocks, so fileContent is typically already comment free + // This is just defensive code, just in case + if (insideComment && line.indexOf('*/') > 0) { + insideComment = false + } else if (!insideComment && line.indexOf('/*') > 0) { + insideComment = true + } + + if (insideComment) { + continue + } + if (!moduleLines.length) { const isModuleDeclaration = line.match(/^\s*declare module.+$/)?.length const moduleIndex = line.indexOf('declare module') diff --git a/src/node/_tasks/dts/_doExtract.ts b/src/node/_tasks/dts/_doExtract.ts index e7975a10..6b2895e3 100644 --- a/src/node/_tasks/dts/_doExtract.ts +++ b/src/node/_tasks/dts/_doExtract.ts @@ -48,13 +48,13 @@ export async function _doExtract( ) const targetPath = path.resolve(cwd, entry.targetPath) - + const filePath = path.relative(outDir, targetPath) const result = await _extractTypes({ customTags: config?.extract?.customTags, cwd, exportPath, files, - filePath: path.relative(outDir, targetPath), + filePath: filePath, projectPath: cwd, rules: config?.extract?.rules, sourceTypesPath: sourceTypesPath, @@ -64,6 +64,7 @@ export async function _doExtract( await _appendModuleBlocks({ tsOutDir: tmpPath, + extractResult: result.extractorResult, extractTypesOutFile: path.resolve(outDir, targetPath), }) diff --git a/test/__snapshots__/cli.test.ts.snap b/test/__snapshots__/cli.test.ts.snap index ae616d64..a6eb79c6 100644 --- a/test/__snapshots__/cli.test.ts.snap +++ b/test/__snapshots__/cli.test.ts.snap @@ -187,3 +187,30 @@ declare interface Plugin_2 { export {} " `; + +exports[`should build ts package 1`] = ` +"/** + * @internal + */ +export declare interface _Dummy { + field: string +} + +/** + * @public + */ +export declare interface IncludedModuleDummy { + field: string +} + +/** @public */ +export declare const VERSION = '1.0.0' + +export {} + +declare module './module2' { + interface IncludedModuleDummy { + addedField: string; + } +}" +`; diff --git a/test/_extractModuleBlocks.test.ts b/test/_extractModuleBlocks.test.ts index 827b259c..3392d7c7 100644 --- a/test/_extractModuleBlocks.test.ts +++ b/test/_extractModuleBlocks.test.ts @@ -26,6 +26,14 @@ test('extract module block', () => { * } * } * / + + /* + declare module 'BB' { + export interface BB { + a: string + } + } + * / ` ) diff --git a/test/cli.test.ts b/test/cli.test.ts index c804ce05..2f055070 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -95,6 +95,20 @@ test('should build `multi-export` package', async () => { await project.remove() }) +test('should build ts package', async () => { + const project = await _spawnProject('ts') + + await project.install() + + const {stdout} = await project.run('build') + + expect(stdout).toContain('./src/index.ts -> ./dist/src/index.d.ts') + + expect(await project.readFile('dist/src/index.d.ts')).toMatchSnapshot() + + await project.remove() +}) + describe.skip('runtime: webpack v3', () => { test('import `dist/*.browser.js` from package', async () => { const exportsDummy = await _spawnProject('exports-dummy') From e8019b274685cd60d06bdcfb316195ea71000e3a Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 11:40:17 +0100 Subject: [PATCH 04/10] chore: have unused code in source dir for testing --- playground/ts/src/index.ts | 3 +++ playground/ts/src/module1.ts | 6 +++--- playground/ts/src/not-imported.ts | 10 ++++++++++ test/cli.test.ts | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 playground/ts/src/not-imported.ts diff --git a/playground/ts/src/index.ts b/playground/ts/src/index.ts index 5a097cbc..a88d5917 100644 --- a/playground/ts/src/index.ts +++ b/playground/ts/src/index.ts @@ -1,3 +1,6 @@ /** @public */ export const VERSION = '1.0.0' export * from './module1' + +//if this line is uncommented, a test should fail +//export * from './not-imported' diff --git a/playground/ts/src/module1.ts b/playground/ts/src/module1.ts index 7834b0a6..ce8e1d7a 100644 --- a/playground/ts/src/module1.ts +++ b/playground/ts/src/module1.ts @@ -15,7 +15,7 @@ declare module './module2' { } /** - * declare namespace ExcludedModule { + * declare module './module2' { * interface TSDocsDummy { * field: string * } @@ -23,14 +23,14 @@ declare module './module2' { */ /* - declare namespace ExcludedModule { + declare module './module2' { interface BlockCommentDummy { field: string } } */ -// declare namespace ExcludedModule { +// declare module './module2' { // interface CommentDummy { // field: string // } diff --git a/playground/ts/src/not-imported.ts b/playground/ts/src/not-imported.ts new file mode 100644 index 00000000..3af0f79b --- /dev/null +++ b/playground/ts/src/not-imported.ts @@ -0,0 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +//@ts-expect-error unused +import {IncludedModuleDummy} from './module2' + +//this should not appear in our final bundle +declare module './module2' { + interface Excluded { + field: string + } +} diff --git a/test/cli.test.ts b/test/cli.test.ts index 2f055070..893d7d9b 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -95,7 +95,7 @@ test('should build `multi-export` package', async () => { await project.remove() }) -test('should build ts package', async () => { +test.only('should build ts package', async () => { const project = await _spawnProject('ts') await project.install() From 1bbaca44f5e7147255d170122d8b37c3644431f3 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 11:43:47 +0100 Subject: [PATCH 05/10] chore: added comment not to remove test-blocks --- playground/ts/src/module1.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playground/ts/src/module1.ts b/playground/ts/src/module1.ts index ce8e1d7a..301856fc 100644 --- a/playground/ts/src/module1.ts +++ b/playground/ts/src/module1.ts @@ -14,6 +14,8 @@ declare module './module2' { } } +// Note: dont remove these blocks, they test that we dont include them in the final bundle + /** * declare module './module2' { * interface TSDocsDummy { From a25c6ad4630a5affba31e734e3c9817e58857981 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 11:45:07 +0100 Subject: [PATCH 06/10] chore: indexOf -> includes --- src/node/_tasks/dts/_declareModuleFix.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/_tasks/dts/_declareModuleFix.ts b/src/node/_tasks/dts/_declareModuleFix.ts index 157ff188..326d32f8 100644 --- a/src/node/_tasks/dts/_declareModuleFix.ts +++ b/src/node/_tasks/dts/_declareModuleFix.ts @@ -60,9 +60,9 @@ export function _extractModuleBlocks(fileContent: string): string[] { for (const line of lines) { // Note: Extractor already removes comment blocks, so fileContent is typically already comment free // This is just defensive code, just in case - if (insideComment && line.indexOf('*/') > 0) { + if (insideComment && line.includes('*/')) { insideComment = false - } else if (!insideComment && line.indexOf('/*') > 0) { + } else if (!insideComment && line.includes('/*')) { insideComment = true } From a04a4d43905c35ad57623ce96d8b3bf24d3a2841 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 11:49:11 +0100 Subject: [PATCH 07/10] chore: test-case for single line out-commented modules --- test/_extractModuleBlocks.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/_extractModuleBlocks.test.ts b/test/_extractModuleBlocks.test.ts index 3392d7c7..a38d8fc6 100644 --- a/test/_extractModuleBlocks.test.ts +++ b/test/_extractModuleBlocks.test.ts @@ -19,6 +19,9 @@ test('extract module block', () => { } } +/*declare module 'sanity' {}*/ + /* declare module 'sanity' {} */ + /** * declare module 'sanity' { * export interface StringOptions { From efa6b4ee281aac40f4cc75ec72e55e447b71c2fb Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 11:53:58 +0100 Subject: [PATCH 08/10] chore: test-case for line with commentblock in the middle --- test/_extractModuleBlocks.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/_extractModuleBlocks.test.ts b/test/_extractModuleBlocks.test.ts index a38d8fc6..f60ac515 100644 --- a/test/_extractModuleBlocks.test.ts +++ b/test/_extractModuleBlocks.test.ts @@ -19,6 +19,7 @@ test('extract module block', () => { } } + const a = 0; /*declare module 'sanity' {}*/ const b = 0; /*declare module 'sanity' {}*/ /* declare module 'sanity' {} */ From b4eee52aa0c280aa699b547a2528e235b4cfb066 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 12:20:54 +0100 Subject: [PATCH 09/10] fix: skip one line comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marius LundgÄrd --- src/node/_tasks/dts/_declareModuleFix.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node/_tasks/dts/_declareModuleFix.ts b/src/node/_tasks/dts/_declareModuleFix.ts index 326d32f8..73e8b367 100644 --- a/src/node/_tasks/dts/_declareModuleFix.ts +++ b/src/node/_tasks/dts/_declareModuleFix.ts @@ -62,6 +62,8 @@ export function _extractModuleBlocks(fileContent: string): string[] { // This is just defensive code, just in case if (insideComment && line.includes('*/')) { insideComment = false + } else if (!insideComment && line.includes('/*') && line.includes('*/')) { + continue // it's a one-line comment } else if (!insideComment && line.includes('/*')) { insideComment = true } From c3050d0a2687341d1501a1fa0b8db4c7494e7e53 Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 4 Nov 2022 12:28:34 +0100 Subject: [PATCH 10/10] chore: remove only --- test/cli.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli.test.ts b/test/cli.test.ts index 893d7d9b..2f055070 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -95,7 +95,7 @@ test('should build `multi-export` package', async () => { await project.remove() }) -test.only('should build ts package', async () => { +test('should build ts package', async () => { const project = await _spawnProject('ts') await project.install()