Skip to content

Commit 95faf3f

Browse files
committed
Ensure overridden ignores are applied
Fixes a regression in 51a7be2.
1 parent 707dec4 commit 95faf3f

File tree

5 files changed

+65
-21
lines changed

5 files changed

+65
-21
lines changed

src/cli.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from 'path';
55
import { program } from 'commander';
66

77
import linguist from './index';
8+
import { normPath } from './helpers/norm-path';
89

910
const colouredMsg = ([r, g, b]: number[], msg: string): string => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
1011
const hexToRgb = (hex: string): number[] => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
@@ -91,7 +92,7 @@ if (args.analyze) (async () => {
9192
if (args.listFiles) {
9293
console.log(); // padding
9394
for (const file of filesPerLanguage[lang]) {
94-
let relFile = path.relative(path.resolve('.'), file).replace(/\\/g, '/');
95+
let relFile = normPath(path.relative(path.resolve('.'), file));
9596
if (!relFile.startsWith('../')) relFile = './' + relFile;
9697
const bytes = (await fs.promises.stat(file)).size;
9798
const fmtd2 = {

src/helpers/norm-path.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import paths from 'path';
2+
3+
export const normPath = function normalisedPath(...inputPaths: string[]) {
4+
return paths.join(...inputPaths).replace(/\\/g, '/');
5+
}
6+
7+
export const normAbsPath = function normalisedAbsolutePath(...inputPaths: string[]) {
8+
return paths.resolve(...inputPaths).replace(/\\/g, '/');
9+
}
10+

src/helpers/parse-gitattributes.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import path from 'path';
2-
31
import * as T from '../types';
2+
import { normPath } from './norm-path';
43

54
export type FlagAttributes = {
65
'vendored': boolean | null,
@@ -27,7 +26,7 @@ export default function parseAttributes(content: string, folderRoot: string = '.
2726

2827
const parts = line.split(/\s+/g);
2928
const fileGlob = parts[0];
30-
const relFileGlob = path.join(folderRoot, fileGlob).replace(/\\/g, '/');
29+
const relFileGlob = normPath(folderRoot, fileGlob);
3130
const attrParts = parts.slice(1);
3231
const isTrue = (str: string) => !str.startsWith('-') && !str.endsWith('=false');
3332
const isFalse = (str: string) => str.startsWith('-') || str.endsWith('=false');

src/helpers/walk-tree.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from 'fs';
22
import paths from 'path';
33
import ignore, { Ignore } from 'ignore';
44
import parseGitignore from './parse-gitignore';
5+
import { normPath, normAbsPath } from './norm-path';
56

67
let allFiles: Set<string>;
78
let allFolders: Set<string>;
@@ -17,8 +18,6 @@ interface WalkInput {
1718
folders: string[],
1819
/** An instantiated Ignore object listing ignored files */
1920
ignored: Ignore,
20-
/** A list of regexes to ignore */
21-
regexIgnores: RegExp[],
2221
};
2322

2423
interface WalkOutput {
@@ -28,7 +27,7 @@ interface WalkOutput {
2827

2928
/** Generate list of files in a directory. */
3029
export default function walk(data: WalkInput): WalkOutput {
31-
const { init, commonRoot, folderRoots, folders, ignored, regexIgnores } = data;
30+
const { init, commonRoot, folderRoots, folders, ignored } = data;
3231

3332
// Initialise files and folders lists
3433
if (init) {
@@ -44,47 +43,46 @@ export default function walk(data: WalkInput): WalkOutput {
4443
// Get list of files and folders inside this folder
4544
const files = fs.readdirSync(folder).map(file => {
4645
// Create path relative to root
47-
const base = paths.resolve(folder, file).replace(/\\/g, '/').replace(commonRoot, '.');
46+
const base = normAbsPath(folder, file).replace(commonRoot, '.');
4847
// Add trailing slash to mark directories
4948
const isDir = fs.lstatSync(paths.resolve(commonRoot, base)).isDirectory();
5049
return isDir ? `${base}/` : base;
5150
});
5251

5352
// Read and apply gitignores
54-
const gitignoreFilename = paths.join(folder, '.gitignore');
53+
const gitignoreFilename = normPath(folder, '.gitignore');
5554
if (fs.existsSync(gitignoreFilename)) {
5655
const gitignoreContents = fs.readFileSync(gitignoreFilename, 'utf-8');
5756
const ignoredPaths = parseGitignore(gitignoreContents);
5857
ignored.add(ignoredPaths);
5958
}
6059

6160
// Add gitattributes if present
62-
const gitattributesPath = paths.join(folder, '.gitattributes');
61+
const gitattributesPath = normPath(folder, '.gitattributes');
6362
if (fs.existsSync(gitattributesPath)) {
6463
allFiles.add(gitattributesPath);
6564
}
6665

6766
// Loop through files and folders
6867
for (const file of files) {
6968
// Create absolute path for disc operations
70-
const path = paths.resolve(commonRoot, file).replace(/\\/g, '/');
69+
const path = normAbsPath(commonRoot, file);
7170
const localPath = localRoot ? file.replace(`./${localRoot}/`, '') : file.replace('./', '');
7271

7372
// Skip if nonexistant
7473
const nonExistant = !fs.existsSync(path);
7574
if (nonExistant) continue;
76-
// Skip if marked as ignored
75+
// Skip if marked in gitignore
7776
const isIgnored = ignored.test(localPath).ignored;
78-
const isRegexIgnored = regexIgnores.some(pattern => pattern.test(localPath));
79-
if (isIgnored || isRegexIgnored) continue;
77+
if (isIgnored) continue;
8078

8179
// Add absolute folder path to list
82-
allFolders.add(paths.resolve(folder).replace(/\\/g, '/'));
80+
allFolders.add(normAbsPath(folder));
8381
// Check if this is a folder or file
8482
if (file.endsWith('/')) {
8583
// Recurse into subfolders
8684
allFolders.add(path);
87-
walk({ init: false, commonRoot, folderRoots, folders: [path], ignored, regexIgnores });
85+
walk({ init: false, commonRoot, folderRoots, folders: [path], ignored });
8886
}
8987
else {
9088
// Add file path to list
@@ -95,7 +93,7 @@ export default function walk(data: WalkInput): WalkOutput {
9593
// Recurse into all folders
9694
else {
9795
for (const i in folders) {
98-
walk({ init: false, commonRoot, folderRoots: [folderRoots[i]], folders: [folders[i]], ignored, regexIgnores });
96+
walk({ init: false, commonRoot, folderRoots: [folderRoots[i]], folders: [folders[i]], ignored });
9997
}
10098
}
10199
// Return absolute files and folders lists

src/index.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import loadFile, { parseGeneratedDataFile } from './helpers/load-data';
1111
import readFile from './helpers/read-file';
1212
import parseAttributes, { FlagAttributes } from './helpers/parse-gitattributes';
1313
import pcre from './helpers/convert-pcre';
14+
import { normPath } from './helpers/norm-path';
1415
import * as T from './types';
1516
import * as S from './schema';
1617

@@ -50,10 +51,8 @@ async function analyse(rawPaths?: string | string[], opts: T.Options = {}): Prom
5051
};
5152

5253
// Set a common root path so that vendor paths do not incorrectly match parent folders
53-
const normPath = (file: string) => file.replace(/\\/g, '/');
5454
const resolvedInput = input.map(path => normPath(paths.resolve(path)));
5555
const commonRoot = (input.length > 1 ? commonPrefix(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
56-
const localRoot = (folder: T.AbsFile): T.RelFile => folder.replace(commonRoot, '').replace(/^\//, '');
5756
const relPath = (file: T.AbsFile): T.RelFile => useRawContent ? file : normPath(paths.relative(commonRoot, file));
5857
const unRelPath = (file: T.RelFile): T.AbsFile => useRawContent ? file : normPath(paths.resolve(commonRoot, file));
5958

@@ -77,7 +76,7 @@ async function analyse(rawPaths?: string | string[], opts: T.Options = {}): Prom
7776
}
7877
else {
7978
// Uses directory on disc
80-
const data = walk({ init: true, commonRoot, folderRoots: resolvedInput, folders: resolvedInput, ignored, regexIgnores });
79+
const data = walk({ init: true, commonRoot, folderRoots: resolvedInput, folders: resolvedInput, ignored });
8180
files = data.files;
8281
}
8382

@@ -86,6 +85,22 @@ async function analyse(rawPaths?: string | string[], opts: T.Options = {}): Prom
8685
const getFlaggedGlobs = (attr: keyof FlagAttributes, val: boolean) => {
8786
return Object.entries(manualAttributes).filter(([, attrs]) => attrs[attr] === val).map(([glob,]) => glob)
8887
};
88+
const findAttrsForPath = (filePath: string): FlagAttributes | null => {
89+
const resultAttrs: Record<string, string | boolean | null> = {};
90+
for (const glob in manualAttributes) {
91+
if (ignore().add(glob).ignores(relPath(filePath))) {
92+
const matchingAttrs = manualAttributes[glob];
93+
for (const [attr, val] of Object.entries(matchingAttrs)) {
94+
if (val !== null) resultAttrs[attr] = val;
95+
}
96+
}
97+
}
98+
99+
if (!JSON.stringify(resultAttrs)) {
100+
return null;
101+
}
102+
return resultAttrs as FlagAttributes;
103+
}
89104
if (!useRawContent && opts.checkAttributes) {
90105
const nestedAttrFiles = files.filter(file => file.endsWith('.gitattributes'));
91106
for (const attrFile of nestedAttrFiles) {
@@ -99,6 +114,27 @@ async function analyse(rawPaths?: string | string[], opts: T.Options = {}): Prom
99114
}
100115
}
101116

117+
// Remove files that are linguist-ignored via regex by default unless explicitly unignored in gitattributes
118+
const filesToIgnore: T.AbsFile[] = [];
119+
for (const file of files) {
120+
const relFile = relPath(file);
121+
122+
const isRegexIgnored = regexIgnores.some(pattern => pattern.test(relFile));
123+
if (!isRegexIgnored) {
124+
// Checking overrides is moot if file is not even marked as ignored by default
125+
continue;
126+
}
127+
128+
const fileAttrs = findAttrsForPath(file);
129+
if (fileAttrs?.generated === false || fileAttrs?.vendored === false) {
130+
// File is explicitly marked as *not* to be ignored
131+
// do nothing
132+
} else {
133+
filesToIgnore.push(file);
134+
}
135+
}
136+
files = files.filter(file => !filesToIgnore.includes(file));
137+
102138
// Apply vendor file path matches and filter out vendored files
103139
if (!opts.keepVendored) {
104140
// Get data of files that have been manually marked with metadata
@@ -361,7 +397,7 @@ async function analyse(rawPaths?: string | string[], opts: T.Options = {}): Prom
361397
if (!useRawContent && opts.relativePaths) {
362398
const newMap: Record<T.RelFile, T.LanguageResult> = {};
363399
for (const [file, lang] of Object.entries(results.files.results)) {
364-
let relPath = paths.relative(process.cwd(), file).replace(/\\/g, '/');
400+
let relPath = normPath(paths.relative(process.cwd(), file));
365401
if (!relPath.startsWith('../')) {
366402
relPath = './' + relPath;
367403
}

0 commit comments

Comments
 (0)