diff --git a/CHANGELOG.md b/CHANGELOG.md index ee561ad18..a0dfc9e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -366,6 +366,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `codegraph sync` and the file watcher now relink an import that reaches a module through a barrel file passing it on under another name, like `import { MatxLoading } from './components'` when `components/index.js` says `export { default as MatxLoading } from './MatxLoading'`, once an edit gives that module what is imported. Before, the import and every call through it stayed unlinked, or stayed on something else in the project with the same name, until the importing file itself changed or the project was indexed again. - A folder named `build` inside `src/` is now indexed for its TypeScript, JSX, Vue, Svelte, Astro, Go, C# and Rust files, so a feature or package kept there, like the `src/build/` pages of luci-go's milo, Go's own `src/go/build` package or the `src/build/` module of dioxus's CLI, shows up in `codegraph_explore`, and the routes that lazily load its pages link to them. Before, every folder named `build` was skipped as build output. The JavaScript, declaration and other files such a folder holds are still left out, since a build may have written them, and so is every `build` folder outside `src/`. - A folder git ignores at the top of your project no longer takes every folder of the same name further down with it: a multi-module Maven project whose root `src/` holds only a gitignored jib agent indexed no Java at all, and a root `lib/` or `static/` that a local build writes hid a package's own `lib/` or an app's `static/` from the index, `codegraph sync` and the file watcher. A gitignored folder whose name reads as a pattern, like `[draft]` or `scratch[1`, now stands for just that folder instead of matching others or making the file watcher fail, and the `includeIgnored` entry `codegraph init` suggests for a skipped nested repository now covers only that repository. Re-index affected projects after upgrading. Thanks @Victor-Rodriguez-Garcia for the report and the fix. (#2849) +- `.gitignore` rules now leave the same files out as git does in a few more cases: a backslash escape like `\d.ts`, a character class like `[[:digit:]]` or one that starts with `]` or `!`, and a run of three asterisks like `logs/***/debug.ts`. A line holding only `!` no longer brings back everything the `.gitignore` leaves out, `node_modules` included. Before, such rules could keep files git ignores in the index and leave out files git keeps, in git repositories and in projects without git alike. The `include`, `exclude` and `includeIgnored` patterns in `codegraph.json` follow the same rules. Re-index affected projects after upgrading. Thanks @johnhldiver-beep. - On Windows, a Swift call written after `try!`, like `try! Message2(data: data)` or `try! Settings.Defaults.load()`, now links to what it calls, as it already did on macOS and Linux. Before, the `!` was read as a logical not, so the call linked to nothing, and a call made through a type lost the type it was made on. Re-index Swift projects on Windows after upgrading. - In JavaScript and TypeScript, a barrel's `export { A, B as C } from './x'` now links each name it passes on to what `./x` exports, following barrels that pass it on in turn. Before, each name was looked up by itself and could link to a same-named symbol elsewhere in the project, such as another folder's `Provider`, and a name passed on from a package, like `export { debounce } from 'lodash'`, could link to a project function of that name. Re-index JavaScript and TypeScript projects after upgrading. - In Swift, a call written after `!` or another prefix operator, or chained after `try` or `await`, now links to what it calls. `if !isReady()` links to `isReady`, `!store.contains(id)` and `try! JSONDecoder().decode(…)` link to the method of the type they're called on rather than to whichever `contains` or `decode` came first, and `!Settings.Defaults.isOn()` lands on the type it names. Re-index Swift projects after upgrading. diff --git a/__tests__/gitignore-pattern-parity.test.ts b/__tests__/gitignore-pattern-parity.test.ts new file mode 100644 index 000000000..9d7e3932a --- /dev/null +++ b/__tests__/gitignore-pattern-parity.test.ts @@ -0,0 +1,68 @@ +/** + * codegraph applies .gitignore rules itself in two places: on the filesystem + * walk (a project without git), for every .gitignore it meets, and in a git + * project on top of `git ls-files`, for its built-in defaults merged with the + * root .gitignore. Both go through the `ignore` package, which before 7.0.14 + * read several pattern forms differently from git: + * - a backslash escape: `\d` was a regex digit, not a literal `d`; + * - a POSIX class such as `[[:digit:]]`, and a `]` or `!` first in a bracket; + * - a run of three asterisks, which git reads as a globstar; + * - a lone `!` line, which matched every path, so it re-included everything + * the .gitignore and codegraph's own defaults (node_modules) leave out. + * Each fixture file is kept or dropped the way git decides, except + * node_modules, which codegraph's defaults always leave out. + */ +import { afterEach, describe, expect, it } from 'vitest'; +import { execFileSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { scanDirectory } from '../src/extraction'; + +const roots: string[] = []; + +afterEach(() => { + for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true }); +}); + +function project(mode: 'walk' | 'git', gitignore: string, files: string[]): string { + const root = fs.mkdtempSync(path.join(os.tmpdir(), `cg-gitignore-parity-${mode}-`)); + roots.push(root); + for (const file of files) { + fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true }); + fs.writeFileSync(path.join(root, file), 'export const value = 1;\n'); + } + fs.writeFileSync(path.join(root, '.gitignore'), gitignore); + if (mode === 'git') execFileSync('git', ['init', '-q'], { cwd: root, stdio: 'ignore' }); + return root; +} + +function scanned(root: string): string[] { + return scanDirectory(root).map((f) => f.replace(/\\/g, '/')).sort(); +} + +describe.each(['walk', 'git'] as const)('.gitignore patterns follow git (%s)', (mode) => { + it('reads escapes, POSIX classes, brackets, *** and trailing spaces as git does', () => { + const root = project( + mode, + [ + '\\d.ts', // a literal "d" + '[[:digit:]]x.ts', // a POSIX class + '[!a]neg.ts', // a negated bracket + '[]]bracket.ts', // "]" first in a bracket is a member + 'stars/***/z.ts', // three asterisks are a globstar + 'trail.ts ', // trailing spaces are trimmed + ].join('\n') + '\n', + [ + '1.ts', 'd.ts', '1x.ts', 'ax.ts', 'aneg.ts', 'bneg.ts', ']bracket.ts', + 'stars/z.ts', 'stars/b/z.ts', 'stars/b/c/z.ts', 'trail.ts', 'other.ts', + ], + ); + expect(scanned(root)).toEqual(['1.ts', 'aneg.ts', 'ax.ts', 'other.ts']); + }); + + it('treats a lone "!" line as no pattern, so ignored files and node_modules stay out', () => { + const root = project(mode, 'skipped.ts\n!\n', ['kept.ts', 'skipped.ts', 'node_modules/pkg/index.ts']); + expect(scanned(root)).toEqual(['kept.ts']); + }); +}); diff --git a/package-lock.json b/package-lock.json index acc69b680..d414c1fbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "commander": "^14.0.2", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", - "ignore": "^7.0.5", + "ignore": "^7.0.14", "jsonc-parser": "^3.3.1", "picomatch": "^4.0.3", "sisteransi": "^1.0.5", @@ -2160,9 +2160,9 @@ } }, "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", "license": "MIT", "engines": { "node": ">= 4" diff --git a/package.json b/package.json index 336ef4ca8..40f33c798 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "commander": "^14.0.2", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", - "ignore": "^7.0.5", + "ignore": "^7.0.14", "jsonc-parser": "^3.3.1", "picomatch": "^4.0.3", "sisteransi": "^1.0.5",