fix: centralize absolute path detection
This commit is contained in:
parent
a6ba6072c0
commit
94fd9d1259
5 changed files with 45 additions and 52 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
* Path resolution utilities for the store.
|
* Path resolution utilities for the store.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { stripTrailingSeparators } from '@shared/utils/platformPath';
|
import { isAbsoluteOrHomePath, stripTrailingSeparators } from '@shared/utils/platformPath';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a relative path against a base path, handling various path formats.
|
* Resolves a relative path against a base path, handling various path formats.
|
||||||
|
|
@ -15,7 +15,7 @@ import { stripTrailingSeparators } from '@shared/utils/platformPath';
|
||||||
*/
|
*/
|
||||||
export function resolveFilePath(base: string, relativePath: string): string {
|
export function resolveFilePath(base: string, relativePath: string): string {
|
||||||
// If already absolute, return as-is
|
// If already absolute, return as-is
|
||||||
if (isAbsolutePath(relativePath)) {
|
if (isAbsoluteOrHomePath(relativePath)) {
|
||||||
return relativePath;
|
return relativePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,13 +27,7 @@ export function resolveFilePath(base: string, relativePath: string): string {
|
||||||
cleanRelative = cleanRelative.slice(1);
|
cleanRelative = cleanRelative.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAbsolutePath(cleanRelative)) {
|
if (isAbsoluteOrHomePath(cleanRelative)) {
|
||||||
return cleanRelative;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tilde paths (~/) are home-relative absolute paths - pass through as-is
|
|
||||||
// The main process will expand ~ to the actual home directory
|
|
||||||
if (cleanRelative.startsWith('~/') || cleanRelative.startsWith('~\\') || cleanRelative === '~') {
|
|
||||||
return cleanRelative;
|
return cleanRelative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,10 +63,6 @@ export function resolveFilePath(base: string, relativePath: string): string {
|
||||||
return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase;
|
return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAbsolutePath(input: string): boolean {
|
|
||||||
return input.startsWith('/') || input.startsWith('\\\\') || /^[a-zA-Z]:[\\/]/.test(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeSeparators(input: string, separator: '/' | '\\'): string {
|
function normalizeSeparators(input: string, separator: '/' | '\\'): string {
|
||||||
let output = '';
|
let output = '';
|
||||||
let prevWasSeparator = false;
|
let prevWasSeparator = false;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
isAbsoluteOrHomePath,
|
||||||
isPathPrefix,
|
isPathPrefix,
|
||||||
lastSeparatorIndex,
|
lastSeparatorIndex,
|
||||||
normalizePathForComparison,
|
normalizePathForComparison,
|
||||||
|
|
@ -63,20 +64,6 @@ export function getDisplayName(path: string, _source: ClaudeMdSource): string {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a path is absolute (starts with /).
|
|
||||||
*/
|
|
||||||
function isAbsolutePath(path: string): boolean {
|
|
||||||
return (
|
|
||||||
path.startsWith('/') ||
|
|
||||||
path.startsWith('~/') ||
|
|
||||||
path.startsWith('~\\') ||
|
|
||||||
path === '~' ||
|
|
||||||
path.startsWith('\\\\') ||
|
|
||||||
/^[a-zA-Z]:[\\/]/.test(path)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join paths, handling various path formats properly.
|
* Join paths, handling various path formats properly.
|
||||||
* Handles:
|
* Handles:
|
||||||
|
|
@ -87,7 +74,7 @@ function isAbsolutePath(path: string): boolean {
|
||||||
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
||||||
*/
|
*/
|
||||||
function joinPaths(base: string, relative: string): string {
|
function joinPaths(base: string, relative: string): string {
|
||||||
if (isAbsolutePath(relative)) {
|
if (isAbsoluteOrHomePath(relative)) {
|
||||||
return relative;
|
return relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +86,7 @@ function joinPaths(base: string, relative: string): string {
|
||||||
if (cleanRelative.startsWith('@')) {
|
if (cleanRelative.startsWith('@')) {
|
||||||
cleanRelative = cleanRelative.slice(1);
|
cleanRelative = cleanRelative.slice(1);
|
||||||
}
|
}
|
||||||
if (isAbsolutePath(cleanRelative)) {
|
if (isAbsoluteOrHomePath(cleanRelative)) {
|
||||||
return cleanRelative;
|
return cleanRelative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,7 +226,9 @@ export function extractUserMentionPaths(
|
||||||
for (const ref of fileReferences) {
|
for (const ref of fileReferences) {
|
||||||
if (ref.path) {
|
if (ref.path) {
|
||||||
// Convert to absolute if relative
|
// Convert to absolute if relative
|
||||||
const absolutePath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path);
|
const absolutePath = isAbsoluteOrHomePath(ref.path)
|
||||||
|
? ref.path
|
||||||
|
: joinPaths(projectRoot, ref.path);
|
||||||
paths.push(absolutePath);
|
paths.push(absolutePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -525,7 +514,7 @@ function computeClaudeMdStats(params: ComputeClaudeMdStatsParams): ClaudeMdStats
|
||||||
const responseRefs = extractFileRefsFromResponses(aiGroup.responses);
|
const responseRefs = extractFileRefsFromResponses(aiGroup.responses);
|
||||||
for (const ref of responseRefs) {
|
for (const ref of responseRefs) {
|
||||||
if (ref.path) {
|
if (ref.path) {
|
||||||
const absPath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path);
|
const absPath = isAbsoluteOrHomePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path);
|
||||||
allFilePaths.push(absPath);
|
allFilePaths.push(absPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,11 @@
|
||||||
* This builds on claudeMdTracker.ts and extends it to track all context sources.
|
* This builds on claudeMdTracker.ts and extends it to track all context sources.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { normalizePathForComparison, stripTrailingSeparators } from '@shared/utils/platformPath';
|
import {
|
||||||
|
isAbsoluteOrHomePath,
|
||||||
|
normalizePathForComparison,
|
||||||
|
stripTrailingSeparators,
|
||||||
|
} from '@shared/utils/platformPath';
|
||||||
import { estimateTokens } from '@shared/utils/tokenFormatting';
|
import { estimateTokens } from '@shared/utils/tokenFormatting';
|
||||||
|
|
||||||
import { MAX_MENTIONED_FILE_TOKENS } from '../types/contextInjection';
|
import { MAX_MENTIONED_FILE_TOKENS } from '../types/contextInjection';
|
||||||
|
|
@ -447,20 +451,6 @@ interface ComputeContextStatsParams {
|
||||||
directoryTokenData?: Record<string, ClaudeMdFileInfo>;
|
directoryTokenData?: Record<string, ClaudeMdFileInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to check if a path is absolute.
|
|
||||||
*/
|
|
||||||
function isAbsolutePath(path: string): boolean {
|
|
||||||
return (
|
|
||||||
path.startsWith('/') ||
|
|
||||||
path.startsWith('~/') ||
|
|
||||||
path.startsWith('~\\') ||
|
|
||||||
path === '~' ||
|
|
||||||
path.startsWith('\\\\') ||
|
|
||||||
/^[a-zA-Z]:[\\/]/.test(path)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to join paths, handling various path formats properly.
|
* Helper to join paths, handling various path formats properly.
|
||||||
* Handles:
|
* Handles:
|
||||||
|
|
@ -471,7 +461,7 @@ function isAbsolutePath(path: string): boolean {
|
||||||
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
||||||
*/
|
*/
|
||||||
function joinPaths(base: string, relative: string): string {
|
function joinPaths(base: string, relative: string): string {
|
||||||
if (isAbsolutePath(relative)) {
|
if (isAbsoluteOrHomePath(relative)) {
|
||||||
return relative;
|
return relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,7 +472,7 @@ function joinPaths(base: string, relative: string): string {
|
||||||
if (cleanRelative.startsWith('@')) {
|
if (cleanRelative.startsWith('@')) {
|
||||||
cleanRelative = cleanRelative.slice(1);
|
cleanRelative = cleanRelative.slice(1);
|
||||||
}
|
}
|
||||||
if (isAbsolutePath(cleanRelative)) {
|
if (isAbsoluteOrHomePath(cleanRelative)) {
|
||||||
return cleanRelative;
|
return cleanRelative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -679,7 +669,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats {
|
||||||
const responseRefs = extractFileRefsFromResponses(aiGroup.responses);
|
const responseRefs = extractFileRefsFromResponses(aiGroup.responses);
|
||||||
for (const ref of responseRefs) {
|
for (const ref of responseRefs) {
|
||||||
if (ref.path) {
|
if (ref.path) {
|
||||||
const absPath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path);
|
const absPath = isAbsoluteOrHomePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path);
|
||||||
allFilePaths.push(absPath);
|
allFilePaths.push(absPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -735,7 +725,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats {
|
||||||
if (!fileRef.path) continue;
|
if (!fileRef.path) continue;
|
||||||
|
|
||||||
// Convert to absolute path if needed
|
// Convert to absolute path if needed
|
||||||
const absolutePath = isAbsolutePath(fileRef.path)
|
const absolutePath = isAbsoluteOrHomePath(fileRef.path)
|
||||||
? fileRef.path
|
? fileRef.path
|
||||||
: joinPaths(projectRoot, fileRef.path);
|
: joinPaths(projectRoot, fileRef.path);
|
||||||
|
|
||||||
|
|
@ -768,7 +758,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats {
|
||||||
for (const fileRef of responseRefs) {
|
for (const fileRef of responseRefs) {
|
||||||
if (!fileRef.path) continue;
|
if (!fileRef.path) continue;
|
||||||
|
|
||||||
const absolutePath = isAbsolutePath(fileRef.path)
|
const absolutePath = isAbsoluteOrHomePath(fileRef.path)
|
||||||
? fileRef.path
|
? fileRef.path
|
||||||
: joinPaths(projectRoot, fileRef.path);
|
: joinPaths(projectRoot, fileRef.path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,18 @@ export function isWindowsishPath(filePath: string): boolean {
|
||||||
return /^[A-Za-z]:\//.test(p) || p.startsWith('//');
|
return /^[A-Za-z]:\//.test(p) || p.startsWith('//');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** True for filesystem-absolute paths and home-relative `~` paths. */
|
||||||
|
export function isAbsoluteOrHomePath(filePath: string): boolean {
|
||||||
|
return (
|
||||||
|
filePath.startsWith('/') ||
|
||||||
|
filePath.startsWith('~/') ||
|
||||||
|
filePath.startsWith('~\\') ||
|
||||||
|
filePath === '~' ||
|
||||||
|
filePath.startsWith('\\\\') ||
|
||||||
|
/^[A-Za-z]:[\\/]/.test(filePath)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize for comparisons:
|
* Normalize for comparisons:
|
||||||
* - Convert `\` → `/`
|
* - Convert `\` → `/`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { getRelativePathWithinPrefix, isPathPrefix } from '../../../src/shared/utils/platformPath';
|
import {
|
||||||
|
getRelativePathWithinPrefix,
|
||||||
|
isAbsoluteOrHomePath,
|
||||||
|
isPathPrefix,
|
||||||
|
} from '../../../src/shared/utils/platformPath';
|
||||||
|
|
||||||
describe('platformPath Windows containment', () => {
|
describe('platformPath Windows containment', () => {
|
||||||
it('matches Windows drive paths case-insensitively and preserves child path style', () => {
|
it('matches Windows drive paths case-insensitively and preserves child path style', () => {
|
||||||
|
|
@ -35,4 +39,12 @@ describe('platformPath Windows containment', () => {
|
||||||
expect(isPathPrefix('', '/Users/Alice/Repo/src/app.ts')).toBe(false);
|
expect(isPathPrefix('', '/Users/Alice/Repo/src/app.ts')).toBe(false);
|
||||||
expect(getRelativePathWithinPrefix('', '/Users/Alice/Repo/src/app.ts')).toBe(null);
|
expect(getRelativePathWithinPrefix('', '/Users/Alice/Repo/src/app.ts')).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('detects absolute and home-relative paths across platforms', () => {
|
||||||
|
expect(isAbsoluteOrHomePath('/Users/Alice/Repo')).toBe(true);
|
||||||
|
expect(isAbsoluteOrHomePath('C:\\Users\\Alice\\Repo')).toBe(true);
|
||||||
|
expect(isAbsoluteOrHomePath('\\\\server\\share\\Repo')).toBe(true);
|
||||||
|
expect(isAbsoluteOrHomePath('~/Repo')).toBe(true);
|
||||||
|
expect(isAbsoluteOrHomePath('src/app.ts')).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue