diff --git a/app/utils/package-content-hints.ts b/app/utils/package-content-hints.ts index 8df8287950..e59e00d8ae 100644 --- a/app/utils/package-content-hints.ts +++ b/app/utils/package-content-hints.ts @@ -11,8 +11,13 @@ const POSSIBLY_UNNECESSARY_FILES: ReadonlySet = new Set([ '.editorconfig', '.prettierignore', '.eslintignore', + '.jshintignore', + '.npmignore', '.gitignore', '.gitattributes', + '.travis.yml', + '.verb.md', + 'Makefile', 'tsconfig.json', '.node-version', '.nvmrc', @@ -27,6 +32,13 @@ const POSSIBLY_UNNECESSARY_FILES: ReadonlySet = new Set([ '.env.production.local', '.nycrc', 'nyc.json', + '.DS_Store', + 'AUTHORS', + 'test.js', + 'test.ts', + 'tests.js', + 'tests.ts', + 'bench.js', ]) const POSSIBLY_UNNECESSARY_DIRECTORIES: ReadonlySet = new Set([ @@ -37,9 +49,11 @@ const POSSIBLY_UNNECESSARY_DIRECTORIES: ReadonlySet = new Set([ '.zed', 'test', 'tests', - '__tests__', 'spec', 'specs', + 'example', + 'examples', + 'benchmark', ]) const POSSIBLY_UNNECESSARY_DIRECTORY_PATTERNS: readonly RegExp[] = [/^__.+__$/] @@ -54,10 +68,16 @@ const POSSIBLY_UNNECESSARY_PATTERNS: readonly RegExp[] = [ /^oxfmt\.config\.(?:js|cjs|mjs|ts|mts|cts)$/, /^\.oxfmtrc(?:\.(?:json|js|cjs|yml|yaml))?$/, // Match common dot-prefixed config files without flagging all dotfiles; - // files like .npmrc, .npmignore, and .gitkeep can be intentional artifacts. + // files like .npmrc can be intentional artifacts. /^\.(?!npmrc$)[a-z][a-z0-9_-]*rc$/, /^\.(?!npmrc\.)[a-z][a-z0-9_-]*rc\.(?:json|js|cjs|mjs|yml|yaml|toml)$/, /^\.[a-z][a-z0-9_-]*\.config\.(?:js|cjs|mjs|ts|mts|cts)$/, + // Match files ending in .test.js, .test.ts, .spec.js, .spec.ts, etc. + /\.(?:test|spec)\.(?:j|t)s$/, + // Match CHANGELOG.md, etc + /^(?:changelog|releasenotes|release-notes|history|contributing|contribute)\.(?:md|markdown|txt)$/i, + // Match example.mjs, examples.js, stc + /^examples?\.(?:js|cjs|mjs|ts|mts|cts)$/, ] export function isPossiblyUnnecessaryContent(name: string, type: 'file' | 'directory'): boolean { diff --git a/test/unit/app/utils/package-content-hints.spec.ts b/test/unit/app/utils/package-content-hints.spec.ts index 770e33d4ea..1640019952 100644 --- a/test/unit/app/utils/package-content-hints.spec.ts +++ b/test/unit/app/utils/package-content-hints.spec.ts @@ -8,6 +8,8 @@ describe('isPossiblyUnnecessaryContent', () => { expect(isPossiblyUnnecessaryContent('.gitattributes', 'file')).toBe(true) expect(isPossiblyUnnecessaryContent('.prettierignore', 'file')).toBe(true) expect(isPossiblyUnnecessaryContent('.eslintignore', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('.jshintignore', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('.npmignore', 'file')).toBe(true) expect(isPossiblyUnnecessaryContent('tsconfig.json', 'file')).toBe(true) }) @@ -30,6 +32,52 @@ describe('isPossiblyUnnecessaryContent', () => { expect(isPossiblyUnnecessaryContent('nyc.json', 'file')).toBe(true) }) + it('flags editor and CI files', () => { + expect(isPossiblyUnnecessaryContent('.travis.yml', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('.verb.md', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('Makefile', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('.DS_Store', 'file')).toBe(true) + }) + + it('flags example and development files', () => { + expect(isPossiblyUnnecessaryContent('test.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('tests.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('test.ts', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('index.test.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('index.test.ts', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('index.spec.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('index.spec.ts', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('bench.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('example.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('examples.js', 'file')).toBe(true) + expect(isPossiblyUnnecessaryContent('example.mjs', 'file')).toBe(true) + }) + + it('flags unnecessay metadata and documentation files', () => { + expect(isPossiblyUnnecessaryContent('AUTHORS', 'file')).toBe(true) + }) + + describe('changelog, release notes, and contributing files', () => { + const files = [ + 'CHANGELOG.md', + 'Changelog.md', + 'changelog.md', + 'CHANGELOG.markdown', + 'changelog.markdown', + 'RELEASENOTES.md', + 'release-notes.md', + 'CONTRIBUTING.md', + 'HISTORY.md', + 'History.md', + 'history.txt', + ] + for (const name of files) { + it(`flags ${name}`, () => { + expect(isPossiblyUnnecessaryContent(name, 'file')).toBe(true) + }) + } + }) + it('matches ESLint configuration patterns', () => { for (const extension of ['js', 'cjs', 'mjs', 'ts', 'mts', 'cts']) { expect(isPossiblyUnnecessaryContent(`eslint.config.${extension}`, 'file')).toBe(true) @@ -105,17 +153,40 @@ describe('isPossiblyUnnecessaryContent', () => { expect(isPossiblyUnnecessaryContent('specs', 'directory')).toBe(true) }) + it('flags development directories', () => { + expect(isPossiblyUnnecessaryContent('benchmark', 'directory')).toBe(true) + }) + + it('flags example directories', () => { + expect(isPossiblyUnnecessaryContent('example', 'directory')).toBe(true) + expect(isPossiblyUnnecessaryContent('examples', 'directory')).toBe(true) + }) + it('does not flag ordinary source files or directories', () => { expect(isPossiblyUnnecessaryContent('index.js', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('latest.js', 'file')).toBe(false) expect(isPossiblyUnnecessaryContent('package.json', 'file')).toBe(false) expect(isPossiblyUnnecessaryContent('README.md', 'file')).toBe(false) - expect(isPossiblyUnnecessaryContent('LICENSE', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('readme.md', 'file')).toBe(false) expect(isPossiblyUnnecessaryContent('main.ts', 'file')).toBe(false) expect(isPossiblyUnnecessaryContent('src', 'directory')).toBe(false) expect(isPossiblyUnnecessaryContent('lib', 'directory')).toBe(false) expect(isPossiblyUnnecessaryContent('dist', 'directory')).toBe(false) }) + it('does not flag legal related files', () => { + expect(isPossiblyUnnecessaryContent('LICENSE', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('LICENCE', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('license', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('licence', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('LICENSE.md', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('LICENCE.md', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('LICENSE-MIT', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('LICENSE-MIT.txt', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('NOTICE', 'file')).toBe(false) + expect(isPossiblyUnnecessaryContent('UNLICENSE', 'file')).toBe(false) + }) + it('does not confuse a directory name passed as a file with the directory match', () => { expect(isPossiblyUnnecessaryContent('.vscode', 'file')).toBe(false) expect(isPossiblyUnnecessaryContent('test', 'file')).toBe(false)