Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const DEFAULT_EXCLUDED_PATH = [
"webpack.config.js",
"**/*.min.js",
"tsconfig.js",
"**.test.**",
"**/*.test.*",
"tests",
];

Expand Down
48 changes: 28 additions & 20 deletions src/fs/glob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,23 @@ export function getParser(
}
}

// Build the ignore function for Glob
export const ignoreFunc = (
filePath: Path,
excludedPatterns: string[],
): boolean => {
return excludedPatterns.some((exclude) => {
/**
* Classify exclude patterns into directory names (for tree pruning)
* and file/glob patterns (for per-file filtering).
*/
export function classifyExcludes(excludedPatterns: string[]) {
const dirs: string[] = [];
const filePatterns: string[] = [];
for (const exclude of excludedPatterns) {
const type = detectPatternType(exclude);
// return true to ignore
switch (type) {
case "file":
return filePath.isNamed(exclude);
case "directory":
return filePath.relative().includes(exclude);
default:
// Handle glob patterns using minimatch
return minimatch(filePath.relative(), exclude);
if (type === "directory") {
dirs.push(exclude);
} else {
filePatterns.push(exclude);
}
}) as boolean;
};
}
return { dirs, filePatterns };
}

/**
* Retrieves a list of files based on the provided arguments and patterns.
Expand All @@ -66,16 +64,26 @@ export const ignoreFunc = (
* @return A promise that resolves to an array of file paths.
*/
export async function getFiles(args: Args, pattern: Patterns): Promise<string[]> {
// 1. Create the Glob instance
const { dirs, filePatterns } = classifyExcludes(pattern.exclude);

const g = new Glob(pattern.include, {
ignore: {
ignored: (p: Path) => ignoreFunc(p, pattern.exclude),
// Prune entire directory subtrees — glob won't enter these dirs at all
childrenIgnored: (p: Path) => dirs.some((d) => p.isNamed(d)),
// Filter individual files by name or glob pattern
ignored: (p: Path) =>
filePatterns.some((fp) => {
const type = detectPatternType(fp);
if (type === "file") {
return p.isNamed(fp);
}
return minimatch(p.relative(), fp);
}),
},
nodir: true,
cwd: args.paths.cwd,
root: args.paths.root ? path.resolve(args.paths.root) : undefined,
});

// 2. Return the walk() promise, which resolves to an array of all file paths
return g.walk();
}
48 changes: 33 additions & 15 deletions src/parser/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ function collectComments(node: SyntaxNode): string | undefined {
return undefined;
}

/**
* Map of escape characters to their resolved values.
* Used by both PHP (encapsed_string) and JS (string) handlers.
*/
const escapeMap: Record<string, string> = {
'n': '\n',
'r': '\r',
't': '\t',
'f': '\f',
'v': '\v',
'0': '\0',
'\\': '\\',
'"': '"',
"'": "'",
'$': '$',
'e': '\x1b',
};

/**
* Resolves the actual string value from a tree-sitter node,
* handling escape sequences in double-quoted strings.
Expand All @@ -42,19 +60,9 @@ function resolveStringValue(node: SyntaxNode): string {
return node.children
.map((child) => {
if (child.type === 'escape_sequence') {
// Unescape common sequences
switch (child.text) {
case '\\n': return '\n';
case '\\r': return '\r';
case '\\t': return '\t';
case '\\\\': return '\\';
case '\\"': return '"';
case '\\$': return '$';
case '\\e': return '\x1b';
case '\\f': return '\f';
case '\\v': return '\v';
default: return child.text;
}
// child.text is e.g. "\\n" — the char after the backslash is the key
const ch = child.text.slice(1);
return ch in escapeMap ? escapeMap[ch] : child.text;
}
// Return literal content
if (child.type === 'string_content') {
Expand All @@ -75,8 +83,18 @@ function resolveStringValue(node: SyntaxNode): string {
// Strip surrounding quotes if present
if ((text.startsWith("'") && text.endsWith("'")) ||
(text.startsWith('"') && text.endsWith('"'))) {
// Remove quotes and unescape escaped quotes
return text.slice(1, -1).replace(/\\'/g, "'").replace(/\\\\/g, "\\");
const isDouble = text.startsWith('"');
let inner = text.slice(1, -1);
if (isDouble) {
// Unescape common escape sequences for double-quoted strings
inner = inner.replace(/\\(.)/g, (_match, ch) =>
ch in escapeMap ? escapeMap[ch] : _match
);
} else {
// Single-quoted: only unescape \\ and \'
inner = inner.replace(/\\'/g, "'").replace(/\\\\/g, "\\");
}
return inner;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function detectPatternType(
pattern: string,
): "file" | "directory" | "glob" {
const containsFileExtension = pattern.includes(".");
const containsDirectorySeparator = pattern.includes(path.sep);
const containsDirectorySeparator = pattern.includes("/");

if (pattern.includes("*")) {
return "glob";
Expand Down