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.
|
||||
*/
|
||||
|
||||
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.
|
||||
|
|
@ -15,7 +15,7 @@ import { stripTrailingSeparators } from '@shared/utils/platformPath';
|
|||
*/
|
||||
export function resolveFilePath(base: string, relativePath: string): string {
|
||||
// If already absolute, return as-is
|
||||
if (isAbsolutePath(relativePath)) {
|
||||
if (isAbsoluteOrHomePath(relativePath)) {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
|
|
@ -27,13 +27,7 @@ export function resolveFilePath(base: string, relativePath: string): string {
|
|||
cleanRelative = cleanRelative.slice(1);
|
||||
}
|
||||
|
||||
if (isAbsolutePath(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 === '~') {
|
||||
if (isAbsoluteOrHomePath(cleanRelative)) {
|
||||
return cleanRelative;
|
||||
}
|
||||
|
||||
|
|
@ -69,10 +63,6 @@ export function resolveFilePath(base: string, relativePath: string): string {
|
|||
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 {
|
||||
let output = '';
|
||||
let prevWasSeparator = false;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
isAbsoluteOrHomePath,
|
||||
isPathPrefix,
|
||||
lastSeparatorIndex,
|
||||
normalizePathForComparison,
|
||||
|
|
@ -63,20 +64,6 @@ export function getDisplayName(path: string, _source: ClaudeMdSource): string {
|
|||
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.
|
||||
* Handles:
|
||||
|
|
@ -87,7 +74,7 @@ function isAbsolutePath(path: string): boolean {
|
|||
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
||||
*/
|
||||
function joinPaths(base: string, relative: string): string {
|
||||
if (isAbsolutePath(relative)) {
|
||||
if (isAbsoluteOrHomePath(relative)) {
|
||||
return relative;
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +86,7 @@ function joinPaths(base: string, relative: string): string {
|
|||
if (cleanRelative.startsWith('@')) {
|
||||
cleanRelative = cleanRelative.slice(1);
|
||||
}
|
||||
if (isAbsolutePath(cleanRelative)) {
|
||||
if (isAbsoluteOrHomePath(cleanRelative)) {
|
||||
return cleanRelative;
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +226,9 @@ export function extractUserMentionPaths(
|
|||
for (const ref of fileReferences) {
|
||||
if (ref.path) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -525,7 +514,7 @@ function computeClaudeMdStats(params: ComputeClaudeMdStatsParams): ClaudeMdStats
|
|||
const responseRefs = extractFileRefsFromResponses(aiGroup.responses);
|
||||
for (const ref of responseRefs) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@
|
|||
* 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 { MAX_MENTIONED_FILE_TOKENS } from '../types/contextInjection';
|
||||
|
|
@ -447,20 +451,6 @@ interface ComputeContextStatsParams {
|
|||
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.
|
||||
* Handles:
|
||||
|
|
@ -471,7 +461,7 @@ function isAbsolutePath(path: string): boolean {
|
|||
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
||||
*/
|
||||
function joinPaths(base: string, relative: string): string {
|
||||
if (isAbsolutePath(relative)) {
|
||||
if (isAbsoluteOrHomePath(relative)) {
|
||||
return relative;
|
||||
}
|
||||
|
||||
|
|
@ -482,7 +472,7 @@ function joinPaths(base: string, relative: string): string {
|
|||
if (cleanRelative.startsWith('@')) {
|
||||
cleanRelative = cleanRelative.slice(1);
|
||||
}
|
||||
if (isAbsolutePath(cleanRelative)) {
|
||||
if (isAbsoluteOrHomePath(cleanRelative)) {
|
||||
return cleanRelative;
|
||||
}
|
||||
|
||||
|
|
@ -679,7 +669,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats {
|
|||
const responseRefs = extractFileRefsFromResponses(aiGroup.responses);
|
||||
for (const ref of responseRefs) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -735,7 +725,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats {
|
|||
if (!fileRef.path) continue;
|
||||
|
||||
// Convert to absolute path if needed
|
||||
const absolutePath = isAbsolutePath(fileRef.path)
|
||||
const absolutePath = isAbsoluteOrHomePath(fileRef.path)
|
||||
? fileRef.path
|
||||
: joinPaths(projectRoot, fileRef.path);
|
||||
|
||||
|
|
@ -768,7 +758,7 @@ function computeContextStats(params: ComputeContextStatsParams): ContextStats {
|
|||
for (const fileRef of responseRefs) {
|
||||
if (!fileRef.path) continue;
|
||||
|
||||
const absolutePath = isAbsolutePath(fileRef.path)
|
||||
const absolutePath = isAbsoluteOrHomePath(fileRef.path)
|
||||
? fileRef.path
|
||||
: joinPaths(projectRoot, fileRef.path);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,18 @@ export function isWindowsishPath(filePath: string): boolean {
|
|||
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:
|
||||
* - Convert `\` → `/`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
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', () => {
|
||||
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(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