Skip to content

Commit 36ee649

Browse files
authored
fix: behavior when using glob in project (#87)
1 parent a8fd8cb commit 36ee649

File tree

4 files changed

+121
-22
lines changed

4 files changed

+121
-22
lines changed

package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,17 @@
6868
"optional": true
6969
}
7070
},
71+
"dependencies": {
72+
"globby": "^11.1.0",
73+
"is-glob": "^4.0.3"
74+
},
7175
"devDependencies": {
7276
"@changesets/changelog-github": "^0.5.0",
7377
"@changesets/cli": "^2.24.2",
7478
"@ota-meshi/eslint-plugin": "^0.15.0",
7579
"@types/chai": "^4.3.0",
7680
"@types/eslint": "^8.0.0",
81+
"@types/is-glob": "^4.0.4",
7782
"@types/mocha": "^10.0.0",
7883
"@types/node": "^20.0.0",
7984
"@types/semver": "^7.3.9",
@@ -96,7 +101,6 @@
96101
"eslint-plugin-svelte": "^2.11.0",
97102
"eslint-plugin-vue": "^9.6.0",
98103
"eslint-plugin-yml": "^1.2.0",
99-
"glob": "^10.3.10",
100104
"mocha": "^10.0.0",
101105
"mocha-chai-jest-snapshot": "^1.1.3",
102106
"nyc": "^15.1.0",

src/index.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ProgramOptions } from "./ts";
33
import { TSServiceManager } from "./ts";
44
import * as tsEslintParser from "@typescript-eslint/parser";
55
import { getProjectConfigFiles } from "./utils/get-project-config-files";
6+
import { resolveProjectList } from "./utils/resolve-project-list";
67
export * as meta from "./meta";
78
export { name } from "./meta";
89

@@ -44,7 +45,20 @@ function* iterateOptions(options: ParserOptions): Iterable<ProgramOptions> {
4445
"Specify `parserOptions.project`. Otherwise there is no point in using this parser.",
4546
);
4647
}
47-
for (const project of getProjectConfigFiles(options)) {
48+
const tsconfigRootDir =
49+
typeof options.tsconfigRootDir === "string"
50+
? options.tsconfigRootDir
51+
: process.cwd();
52+
53+
for (const project of resolveProjectList({
54+
project: getProjectConfigFiles({
55+
project: options.project,
56+
tsconfigRootDir,
57+
filePath: options.filePath,
58+
}),
59+
projectFolderIgnoreList: options.projectFolderIgnoreList,
60+
tsconfigRootDir,
61+
})) {
4862
yield {
4963
project,
5064
filePath: options.filePath,

src/utils/get-project-config-files.ts

+14-20
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
1-
import type { ParserOptions } from "@typescript-eslint/parser";
21
import fs from "fs";
3-
import { glob } from "glob";
42
import path from "path";
53

6-
function syncWithGlob(pattern: string, cwd: string): string[] {
7-
return glob
8-
.sync(pattern, { cwd })
9-
.map((filePath) => path.resolve(cwd, filePath));
10-
}
11-
12-
export function getProjectConfigFiles(options: ParserOptions): string[] {
13-
const tsconfigRootDir =
14-
typeof options.tsconfigRootDir === "string"
15-
? options.tsconfigRootDir
16-
: process.cwd();
17-
4+
export function getProjectConfigFiles(
5+
options: Readonly<{
6+
project: string[] | string | true | null | undefined;
7+
tsconfigRootDir: string;
8+
filePath?: string;
9+
}>,
10+
): string[] {
1811
if (options.project !== true) {
1912
return Array.isArray(options.project)
20-
? options.project.flatMap((projectPattern: string) =>
21-
syncWithGlob(projectPattern, tsconfigRootDir),
22-
)
23-
: syncWithGlob(options.project!, tsconfigRootDir);
13+
? options.project
14+
: [options.project!];
2415
}
2516

2617
let directory = path.dirname(options.filePath!);
@@ -34,9 +25,12 @@ export function getProjectConfigFiles(options: ParserOptions): string[] {
3425

3526
directory = path.dirname(directory);
3627
checkedDirectories.push(directory);
37-
} while (directory.length > 1 && directory.length >= tsconfigRootDir.length);
28+
} while (
29+
directory.length > 1 &&
30+
directory.length >= options.tsconfigRootDir.length
31+
);
3832

3933
throw new Error(
40-
`project was set to \`true\` but couldn't find any tsconfig.json relative to '${options.filePath}' within '${tsconfigRootDir}'.`,
34+
`project was set to \`true\` but couldn't find any tsconfig.json relative to '${options.filePath}' within '${options.tsconfigRootDir}'.`,
4135
);
4236
}

src/utils/resolve-project-list.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { sync as globSync } from "globby";
2+
import isGlob from "is-glob";
3+
import path from "path";
4+
import * as ts from "typescript";
5+
6+
/**
7+
* Normalizes, sanitizes, resolves and filters the provided project paths
8+
*/
9+
export function resolveProjectList(
10+
options: Readonly<{
11+
project: string[] | null;
12+
projectFolderIgnoreList: (RegExp | string)[] | undefined;
13+
tsconfigRootDir: string;
14+
}>,
15+
): readonly string[] {
16+
const sanitizedProjects: string[] = [];
17+
18+
// Normalize and sanitize the project paths
19+
if (options.project != null) {
20+
for (const project of options.project) {
21+
if (typeof project === "string") {
22+
sanitizedProjects.push(project);
23+
}
24+
}
25+
}
26+
27+
if (sanitizedProjects.length === 0) {
28+
return [];
29+
}
30+
31+
const projectFolderIgnoreList = (
32+
options.projectFolderIgnoreList ?? ["**/node_modules/**"]
33+
)
34+
.reduce<string[]>((acc, folder) => {
35+
if (typeof folder === "string") {
36+
acc.push(folder);
37+
}
38+
return acc;
39+
}, [])
40+
// prefix with a ! for not match glob
41+
.map((folder) => (folder.startsWith("!") ? folder : `!${folder}`));
42+
43+
// Transform glob patterns into paths
44+
const nonGlobProjects = sanitizedProjects.filter(
45+
(project) => !isGlob(project),
46+
);
47+
const globProjects = sanitizedProjects.filter((project) => isGlob(project));
48+
49+
const uniqueCanonicalProjectPaths = new Set(
50+
nonGlobProjects
51+
.concat(
52+
globProjects.length === 0
53+
? []
54+
: globSync([...globProjects, ...projectFolderIgnoreList], {
55+
cwd: options.tsconfigRootDir,
56+
}),
57+
)
58+
.map((project) =>
59+
getCanonicalFileName(
60+
ensureAbsolutePath(project, options.tsconfigRootDir),
61+
),
62+
),
63+
);
64+
65+
return Array.from(uniqueCanonicalProjectPaths);
66+
}
67+
68+
// typescript doesn't provide a ts.sys implementation for browser environments
69+
const useCaseSensitiveFileNames =
70+
ts.sys !== undefined ? ts.sys.useCaseSensitiveFileNames : true;
71+
const correctPathCasing = useCaseSensitiveFileNames
72+
? (filePath: string): string => filePath
73+
: (filePath: string): string => filePath.toLowerCase();
74+
75+
function getCanonicalFileName(filePath: string): string {
76+
let normalized = path.normalize(filePath);
77+
if (normalized.endsWith(path.sep)) {
78+
normalized = normalized.slice(0, -1);
79+
}
80+
return correctPathCasing(normalized);
81+
}
82+
83+
function ensureAbsolutePath(p: string, tsconfigRootDir: string): string {
84+
return path.isAbsolute(p)
85+
? p
86+
: path.join(tsconfigRootDir || process.cwd(), p);
87+
}

0 commit comments

Comments
 (0)