Skip to content

Commit

Permalink
Properly fix file path resolutions
Browse files Browse the repository at this point in the history
  • Loading branch information
Nixinova committed Jul 23, 2023
1 parent 5fbe780 commit 476d3fd
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 22 deletions.
4 changes: 2 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Changelog

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

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

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

Expand Down
42 changes: 31 additions & 11 deletions src/helpers/walk-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,26 @@ import { Ignore } from 'ignore';
let allFiles: Set<string>;
let allFolders: Set<string>;

interface WalkInput {
/** Whether this is walking the tree from the root */
init: boolean,
/** The common root absolute path of all folders being checked */
commonRoot: string,
/** The absolute path that each folder is relative to */
folderRoots: string[],
/** The absolute path of folders being checked */
folders: string[],
gitignores: Ignore,
regexIgnores: RegExp[],
};
interface WalkOutput {
files: string[],
folders: string[],
};

/** Generate list of files in a directory. */
export default function walk(init: boolean, root: string, folders: string[], gitignores: Ignore, regexIgnores: RegExp[]): { files: string[], folders: string[] } {
export default function walk(data: WalkInput): WalkOutput {
const { init, commonRoot, folderRoots, folders, gitignores, regexIgnores } = data;

// Initialise files and folders lists
if (init) {
Expand All @@ -17,46 +35,48 @@ export default function walk(init: boolean, root: string, folders: string[], git
// Walk tree of a folder
if (folders.length === 1) {
const folder = folders[0];
const localRoot = folderRoots[0].replace(commonRoot, '').replace(/^\//, '');
// Get list of files and folders inside this folder
const files = fs.readdirSync(folder).map(file => {
// Create path relative to root
const base = paths.resolve(folder, file).replace(/\\/g, '/').replace(root, '.');
const base = paths.resolve(folder, file).replace(/\\/g, '/').replace(commonRoot, '.');
// Add trailing slash to mark directories
const isDir = fs.lstatSync(paths.resolve(root, base)).isDirectory();
const isDir = fs.lstatSync(paths.resolve(commonRoot, base)).isDirectory();
return isDir ? `${base}/` : base;
});
// Loop through files and folders
for (const file of files) {
// Create absolute path for disc operations
const path = paths.resolve(root, file).replace(/\\/g, '/');
const path = paths.resolve(commonRoot, file).replace(/\\/g, '/');
const localPath = localRoot ? file.replace(`./${localRoot}/`, '') : file.replace('./', '');
// Skip if nonexistant or ignored
const nonExistant = !fs.existsSync(path);
const isGitIgnored = gitignores.test(file.replace('./', '')).ignored;
const isRegexIgnored = regexIgnores.find(match => file.replace('./', '').match(match));
const isGitIgnored = gitignores.test(localPath).ignored;
const isRegexIgnored = regexIgnores.find(match => localPath.match(match));
if (nonExistant || isGitIgnored || isRegexIgnored) continue;
// Add absolute folder path to list
allFolders.add(paths.resolve(folder).replace(/\\/g, '/'));
// Check if this is a folder or file
if (file.endsWith('/')) {
// Recurse into subfolders
allFolders.add(path);
walk(false, root, [path], gitignores, regexIgnores);
walk({ init: false, commonRoot: commonRoot, folderRoots, folders: [path], gitignores, regexIgnores });
}
else {
// Add relative file path to list
// Add file path to list
allFiles.add(path);
}
}
}
// Recurse into all folders
else {
for (const path of folders) {
walk(false, root, [path], gitignores, regexIgnores);
for (const i in folders) {
walk({ init: false, commonRoot: commonRoot, folderRoots: [folderRoots[i]], folders: [folders[i]], gitignores, regexIgnores });
}
}
// Return absolute files and folders lists
return {
files: [...allFiles].map(file => file.replace(/^\./, root)),
files: [...allFiles].map(file => file.replace(/^\./, commonRoot)),
folders: [...allFolders],
};
}
24 changes: 15 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
if (opts.ignoredFiles) gitignores.add(opts.ignoredFiles);

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

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

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

// Parse gitattributes
Expand All @@ -111,16 +117,16 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
for (const [_line, path, type] of contentTypeMatches) {
if (['text', '-binary'].includes(type)) {
customText.add(path);
customText.add(localFilePath(path));
}
if (['-text', 'binary'].includes(type)) {
customBinary.add(path);
customBinary.add(localFilePath(path));
}
}
// Custom vendor options
const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
for (const [_line, path] of vendorMatches) {
gitignores.add(path);
gitignores.add(localFilePath(path));
}
// Custom file associations
const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
Expand All @@ -147,7 +153,7 @@ async function analyse(input?: string | string[], opts: T.Options = {}): Promise
files = files.filter(file => !regexIgnores.find(match => match.test(file)));
}
else {
files = gitignores.filter(files.map(relPath)).map(unRelPath);
files = gitignores.filter(files.map(localPath)).map(unRelPath);
}
}

Expand Down

0 comments on commit 476d3fd

Please sign in to comment.