Skip to content

Commit 476d3fd

Browse files
committed
Properly fix file path resolutions
1 parent 5fbe780 commit 476d3fd

File tree

3 files changed

+48
-22
lines changed

3 files changed

+48
-22
lines changed

changelog.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Changelog
22

33
## Next
4-
- Fixed the `.git` folder not being ignored when analysing multiple folders ([#25](https://github.com/Nixinova/LinguistJS/issues/25)).
4+
- Fixed file paths not resolving properly when analysing multiple folders ([#25](https://github.com/Nixinova/LinguistJS/issues/25)).
55

66
## 2.6.0
77
*2023-06-29*
@@ -21,7 +21,7 @@
2121
*2023-01-11*
2222
- Fixed gitattributes wildcards not being applied into subfolders ([#17](https://github.com/Nixinova/LinguistJS/issues/17)).
2323

24-
# 2.5.3
24+
## 2.5.3
2525
*2022-09-03*
2626
- Fixed a crash occurring when parsing heuristics for `.txt` files ([#16](https://github.com/Nixinova/LinguistJS/issues/16)).
2727

src/helpers/walk-tree.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,26 @@ import { Ignore } from 'ignore';
55
let allFiles: Set<string>;
66
let allFolders: Set<string>;
77

8+
interface WalkInput {
9+
/** Whether this is walking the tree from the root */
10+
init: boolean,
11+
/** The common root absolute path of all folders being checked */
12+
commonRoot: string,
13+
/** The absolute path that each folder is relative to */
14+
folderRoots: string[],
15+
/** The absolute path of folders being checked */
16+
folders: string[],
17+
gitignores: Ignore,
18+
regexIgnores: RegExp[],
19+
};
20+
interface WalkOutput {
21+
files: string[],
22+
folders: string[],
23+
};
24+
825
/** Generate list of files in a directory. */
9-
export default function walk(init: boolean, root: string, folders: string[], gitignores: Ignore, regexIgnores: RegExp[]): { files: string[], folders: string[] } {
26+
export default function walk(data: WalkInput): WalkOutput {
27+
const { init, commonRoot, folderRoots, folders, gitignores, regexIgnores } = data;
1028

1129
// Initialise files and folders lists
1230
if (init) {
@@ -17,46 +35,48 @@ export default function walk(init: boolean, root: string, folders: string[], git
1735
// Walk tree of a folder
1836
if (folders.length === 1) {
1937
const folder = folders[0];
38+
const localRoot = folderRoots[0].replace(commonRoot, '').replace(/^\//, '');
2039
// Get list of files and folders inside this folder
2140
const files = fs.readdirSync(folder).map(file => {
2241
// Create path relative to root
23-
const base = paths.resolve(folder, file).replace(/\\/g, '/').replace(root, '.');
42+
const base = paths.resolve(folder, file).replace(/\\/g, '/').replace(commonRoot, '.');
2443
// Add trailing slash to mark directories
25-
const isDir = fs.lstatSync(paths.resolve(root, base)).isDirectory();
44+
const isDir = fs.lstatSync(paths.resolve(commonRoot, base)).isDirectory();
2645
return isDir ? `${base}/` : base;
2746
});
2847
// Loop through files and folders
2948
for (const file of files) {
3049
// Create absolute path for disc operations
31-
const path = paths.resolve(root, file).replace(/\\/g, '/');
50+
const path = paths.resolve(commonRoot, file).replace(/\\/g, '/');
51+
const localPath = localRoot ? file.replace(`./${localRoot}/`, '') : file.replace('./', '');
3252
// Skip if nonexistant or ignored
3353
const nonExistant = !fs.existsSync(path);
34-
const isGitIgnored = gitignores.test(file.replace('./', '')).ignored;
35-
const isRegexIgnored = regexIgnores.find(match => file.replace('./', '').match(match));
54+
const isGitIgnored = gitignores.test(localPath).ignored;
55+
const isRegexIgnored = regexIgnores.find(match => localPath.match(match));
3656
if (nonExistant || isGitIgnored || isRegexIgnored) continue;
3757
// Add absolute folder path to list
3858
allFolders.add(paths.resolve(folder).replace(/\\/g, '/'));
3959
// Check if this is a folder or file
4060
if (file.endsWith('/')) {
4161
// Recurse into subfolders
4262
allFolders.add(path);
43-
walk(false, root, [path], gitignores, regexIgnores);
63+
walk({ init: false, commonRoot: commonRoot, folderRoots, folders: [path], gitignores, regexIgnores });
4464
}
4565
else {
46-
// Add relative file path to list
66+
// Add file path to list
4767
allFiles.add(path);
4868
}
4969
}
5070
}
5171
// Recurse into all folders
5272
else {
53-
for (const path of folders) {
54-
walk(false, root, [path], gitignores, regexIgnores);
73+
for (const i in folders) {
74+
walk({ init: false, commonRoot: commonRoot, folderRoots: [folderRoots[i]], folders: [folders[i]], gitignores, regexIgnores });
5575
}
5676
}
5777
// Return absolute files and folders lists
5878
return {
59-
files: [...allFiles].map(file => file.replace(/^\./, root)),
79+
files: [...allFiles].map(file => file.replace(/^\./, commonRoot)),
6080
folders: [...allFolders],
6181
};
6282
}

src/index.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
4646
if (opts.ignoredFiles) gitignores.add(opts.ignoredFiles);
4747

4848
// Set a common root path so that vendor paths do not incorrectly match parent folders
49-
const resolvedInput = input.map(path => paths.resolve(path).replace(/\\/g, '/'));
49+
const normPath = (file: string) => file.replace(/\\/g, '/');
50+
const resolvedInput = input.map(path => normPath(paths.resolve(path)));
5051
const commonRoot = (input.length > 1 ? commonPrefix(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
51-
const relPath = (file: string) => paths.relative(commonRoot, file).replace(/\\/g, '/');
52-
const unRelPath = (file: string) => paths.resolve(commonRoot, file).replace(/\\/g, '/');
52+
const localRoot = (folder: string) => folder.replace(commonRoot, '').replace(/^\//, '');
53+
const relPath = (file: string) => normPath(paths.relative(commonRoot, file));
54+
const unRelPath = (file: string) => normPath(paths.resolve(commonRoot, file));
55+
const localPath = (file: string) => localRoot(unRelPath(file));
5356

5457
// Load file paths and folders
5558
let files, folders;
@@ -60,7 +63,7 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
6063
}
6164
else {
6265
// Uses directory on disc
63-
const data = walk(true, commonRoot, input, gitignores, regexIgnores);
66+
const data = walk({ init: true, commonRoot, folderRoots: resolvedInput, folders: resolvedInput, gitignores, regexIgnores });
6467
files = data.files;
6568
folders = data.folders;
6669
}
@@ -90,6 +93,8 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
9093
const customText = ignore();
9194
if (!useRawContent && opts.checkAttributes) {
9295
for (const folder of folders) {
96+
// TODO FIX: this is absolute when only 1 path given
97+
const localFilePath = (path: string) => localRoot(folder) ? localRoot(folder) + '/' + localPath(path) : path;
9398

9499
// Skip if folder is marked in gitattributes
95100
if (relPath(folder) && gitignores.ignores(relPath(folder))) {
@@ -100,7 +105,8 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
100105
const ignoresFile = paths.join(folder, '.gitignore');
101106
if (opts.checkIgnored && fs.existsSync(ignoresFile)) {
102107
const ignoresData = await readFile(ignoresFile);
103-
gitignores.add(ignoresData);
108+
const localIgnoresData = ignoresData.replace(/^[\/\\]/g, localRoot(folder) + '/');
109+
gitignores.add(localIgnoresData);
104110
}
105111

106112
// Parse gitattributes
@@ -111,16 +117,16 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
111117
const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
112118
for (const [_line, path, type] of contentTypeMatches) {
113119
if (['text', '-binary'].includes(type)) {
114-
customText.add(path);
120+
customText.add(localFilePath(path));
115121
}
116122
if (['-text', 'binary'].includes(type)) {
117-
customBinary.add(path);
123+
customBinary.add(localFilePath(path));
118124
}
119125
}
120126
// Custom vendor options
121127
const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
122128
for (const [_line, path] of vendorMatches) {
123-
gitignores.add(path);
129+
gitignores.add(localFilePath(path));
124130
}
125131
// Custom file associations
126132
const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
@@ -147,7 +153,7 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
147153
files = files.filter(file => !regexIgnores.find(match => match.test(file)));
148154
}
149155
else {
150-
files = gitignores.filter(files.map(relPath)).map(unRelPath);
156+
files = gitignores.filter(files.map(localPath)).map(unRelPath);
151157
}
152158
}
153159

0 commit comments

Comments
 (0)