mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-13 08:51:28 +00:00
fix(r): read the DESCRIPTION Package field past a byte-order mark
A UTF-8 byte-order mark before `Package:` hid the package, so its qualified calls were dropped as external. And `\s*` crossed a newline, so an empty `Package:` field captured the next line's `Version:` as the package name. Strip the mark and keep the value on the field's own line.
This commit is contained in:
parent
2566751ba1
commit
76acfd2b59
2 changed files with 61 additions and 5 deletions
|
|
@ -237,6 +237,16 @@ function preferredPackageDir(pkgDir: string): string {
|
|||
/** Global directory budget for {@link hidesUndiscoveredPackage} across all its roots. */
|
||||
const HIDDEN_SCAN_BUDGET = 5000;
|
||||
|
||||
/**
|
||||
* The `Package:` field of a `DESCRIPTION` file's text, or `undefined` when it has none. A leading
|
||||
* UTF-8 byte-order mark is not part of the first field name, and a field with an empty value
|
||||
* names nothing: `[ \t]*`, not `\s*`, so the value never begins on the next line.
|
||||
*/
|
||||
function descriptionPackageName(content: string): string | undefined {
|
||||
const text = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
||||
return /^Package:[ \t]*(\S+)/m.exec(text)?.[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the subtrees the main walk skipped for a package it never discovered.
|
||||
*
|
||||
|
|
@ -271,8 +281,8 @@ export async function hidesUndiscoveredPackage(
|
|||
} else if (entry.isFile() && entry.name === 'DESCRIPTION') {
|
||||
try {
|
||||
const content = await fs.readFile(path.join(dir, entry.name), 'utf-8');
|
||||
const pkgMatch = content.match(/^Package:\s*(\S+)/m);
|
||||
if (pkgMatch && !knownPackageNames.has(pkgMatch[1])) return true;
|
||||
const pkgName = descriptionPackageName(content);
|
||||
if (pkgName !== undefined && !knownPackageNames.has(pkgName)) return true;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -325,9 +335,8 @@ export async function loadRPackageConfig(repoRoot: string): Promise<RPackageConf
|
|||
try {
|
||||
const descPath = path.join(dir, entry.name);
|
||||
const content = await fs.readFile(descPath, 'utf-8');
|
||||
const pkgMatch = content.match(/^Package:\s*(\S+)/m);
|
||||
if (pkgMatch) {
|
||||
const pkgName = pkgMatch[1];
|
||||
const pkgName = descriptionPackageName(content);
|
||||
if (pkgName !== undefined) {
|
||||
const pkgDir = path.relative(repoRoot, dir).replace(/\\/g, '/');
|
||||
packageDirs.add(pkgDir);
|
||||
// Same name in several directories: keep the preferred one, whatever the
|
||||
|
|
|
|||
47
gitnexus/test/unit/r-description-package-field.test.ts
Normal file
47
gitnexus/test/unit/r-description-package-field.test.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* The `Package:` field of a `DESCRIPTION` file names a package even when the file starts with a
|
||||
* UTF-8 byte-order mark, and a field with an empty value names nothing rather than capturing the
|
||||
* name of the next line's field. Both discovery paths read the field the same way: the package
|
||||
* walk (`loadRPackageConfig`) and the hidden-package scan (`hidesUndiscoveredPackage`).
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
hidesUndiscoveredPackage,
|
||||
loadRPackageConfig,
|
||||
} from '../../src/core/ingestion/languages/r/package-config.js';
|
||||
import { createTempDirPool } from '../helpers/temp-dir-pool.js';
|
||||
|
||||
const pool = createTempDirPool('gn-r-desc-');
|
||||
|
||||
const repoWith = (description: string): string => {
|
||||
const root = pool.dir();
|
||||
fs.writeFileSync(path.join(root, 'DESCRIPTION'), description);
|
||||
return root;
|
||||
};
|
||||
|
||||
describe('DESCRIPTION Package: field', () => {
|
||||
it('discovers a package whose DESCRIPTION starts with a UTF-8 byte-order mark', async () => {
|
||||
const config = await loadRPackageConfig(repoWith('Package: bompkg\nVersion: 1.0\n'));
|
||||
expect([...(config?.packages ?? [])]).toEqual([['bompkg', '']]);
|
||||
});
|
||||
|
||||
it('sees a byte-order-marked package when scanning for a hidden one', async () => {
|
||||
const root = repoWith('Package: bompkg\nVersion: 1.0\n');
|
||||
expect(await hidesUndiscoveredPackage([root], new Set())).toBe(true);
|
||||
expect(await hidesUndiscoveredPackage([root], new Set(['bompkg']))).toBe(false);
|
||||
});
|
||||
|
||||
it('does not take the next line as the value of an empty Package: field', async () => {
|
||||
const config = await loadRPackageConfig(repoWith('Package:\nVersion: 1.0\n'));
|
||||
expect([...(config?.packages.keys() ?? [])]).toEqual([]);
|
||||
const root = repoWith('Package:\nVersion: 1.0\n');
|
||||
expect(await hidesUndiscoveredPackage([root], new Set())).toBe(false);
|
||||
});
|
||||
|
||||
it('still reads an ordinary field, with CRLF line endings and extra blanks', async () => {
|
||||
const config = await loadRPackageConfig(repoWith('Package: spaced\r\nVersion: 1.0\r\n'));
|
||||
expect([...(config?.packages.keys() ?? [])]).toEqual(['spaced']);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue