Skip to content

fix: behavior when using glob in project #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 13, 2024
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
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,17 @@
"optional": true
}
},
"dependencies": {
"globby": "^11.1.0",
"is-glob": "^4.0.3"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.24.2",
"@ota-meshi/eslint-plugin": "^0.15.0",
"@types/chai": "^4.3.0",
"@types/eslint": "^8.0.0",
"@types/is-glob": "^4.0.4",
"@types/mocha": "^10.0.0",
"@types/node": "^20.0.0",
"@types/semver": "^7.3.9",
Expand All @@ -96,7 +101,6 @@
"eslint-plugin-svelte": "^2.11.0",
"eslint-plugin-vue": "^9.6.0",
"eslint-plugin-yml": "^1.2.0",
"glob": "^10.3.10",
"mocha": "^10.0.0",
"mocha-chai-jest-snapshot": "^1.1.3",
"nyc": "^15.1.0",
Expand Down
16 changes: 15 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ProgramOptions } from "./ts";
import { TSServiceManager } from "./ts";
import * as tsEslintParser from "@typescript-eslint/parser";
import { getProjectConfigFiles } from "./utils/get-project-config-files";
import { resolveProjectList } from "./utils/resolve-project-list";
export * as meta from "./meta";
export { name } from "./meta";

Expand Down Expand Up @@ -44,7 +45,20 @@ function* iterateOptions(options: ParserOptions): Iterable<ProgramOptions> {
"Specify `parserOptions.project`. Otherwise there is no point in using this parser.",
);
}
for (const project of getProjectConfigFiles(options)) {
const tsconfigRootDir =
typeof options.tsconfigRootDir === "string"
? options.tsconfigRootDir
: process.cwd();

for (const project of resolveProjectList({
project: getProjectConfigFiles({
project: options.project,
tsconfigRootDir,
filePath: options.filePath,
}),
projectFolderIgnoreList: options.projectFolderIgnoreList,
tsconfigRootDir,
})) {
yield {
project,
filePath: options.filePath,
Expand Down
34 changes: 14 additions & 20 deletions src/utils/get-project-config-files.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
import type { ParserOptions } from "@typescript-eslint/parser";
import fs from "fs";
import { glob } from "glob";
import path from "path";

function syncWithGlob(pattern: string, cwd: string): string[] {
return glob
.sync(pattern, { cwd })
.map((filePath) => path.resolve(cwd, filePath));
}

export function getProjectConfigFiles(options: ParserOptions): string[] {
const tsconfigRootDir =
typeof options.tsconfigRootDir === "string"
? options.tsconfigRootDir
: process.cwd();

export function getProjectConfigFiles(
options: Readonly<{
project: string[] | string | true | null | undefined;
tsconfigRootDir: string;
filePath?: string;
}>,
): string[] {
if (options.project !== true) {
return Array.isArray(options.project)
? options.project.flatMap((projectPattern: string) =>
syncWithGlob(projectPattern, tsconfigRootDir),
)
: syncWithGlob(options.project!, tsconfigRootDir);
? options.project
: [options.project!];
}

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

directory = path.dirname(directory);
checkedDirectories.push(directory);
} while (directory.length > 1 && directory.length >= tsconfigRootDir.length);
} while (
directory.length > 1 &&
directory.length >= options.tsconfigRootDir.length
);

throw new Error(
`project was set to \`true\` but couldn't find any tsconfig.json relative to '${options.filePath}' within '${tsconfigRootDir}'.`,
`project was set to \`true\` but couldn't find any tsconfig.json relative to '${options.filePath}' within '${options.tsconfigRootDir}'.`,
);
}
87 changes: 87 additions & 0 deletions src/utils/resolve-project-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { sync as globSync } from "globby";
import isGlob from "is-glob";
import path from "path";
import * as ts from "typescript";

/**
* Normalizes, sanitizes, resolves and filters the provided project paths
*/
export function resolveProjectList(
options: Readonly<{
project: string[] | null;
projectFolderIgnoreList: (RegExp | string)[] | undefined;
tsconfigRootDir: string;
}>,
): readonly string[] {
const sanitizedProjects: string[] = [];

// Normalize and sanitize the project paths
if (options.project != null) {
for (const project of options.project) {
if (typeof project === "string") {
sanitizedProjects.push(project);
}
}
}

if (sanitizedProjects.length === 0) {
return [];
}

const projectFolderIgnoreList = (
options.projectFolderIgnoreList ?? ["**/node_modules/**"]
)
.reduce<string[]>((acc, folder) => {
if (typeof folder === "string") {
acc.push(folder);
}
return acc;
}, [])
// prefix with a ! for not match glob
.map((folder) => (folder.startsWith("!") ? folder : `!${folder}`));

// Transform glob patterns into paths
const nonGlobProjects = sanitizedProjects.filter(
(project) => !isGlob(project),
);
const globProjects = sanitizedProjects.filter((project) => isGlob(project));

const uniqueCanonicalProjectPaths = new Set(
nonGlobProjects
.concat(
globProjects.length === 0
? []
: globSync([...globProjects, ...projectFolderIgnoreList], {
cwd: options.tsconfigRootDir,
}),
)
.map((project) =>
getCanonicalFileName(
ensureAbsolutePath(project, options.tsconfigRootDir),
),
),
);

return Array.from(uniqueCanonicalProjectPaths);
}

// typescript doesn't provide a ts.sys implementation for browser environments
const useCaseSensitiveFileNames =
ts.sys !== undefined ? ts.sys.useCaseSensitiveFileNames : true;
const correctPathCasing = useCaseSensitiveFileNames
? (filePath: string): string => filePath
: (filePath: string): string => filePath.toLowerCase();

function getCanonicalFileName(filePath: string): string {
let normalized = path.normalize(filePath);
if (normalized.endsWith(path.sep)) {
normalized = normalized.slice(0, -1);
}
return correctPathCasing(normalized);
}

function ensureAbsolutePath(p: string, tsconfigRootDir: string): string {
return path.isAbsolute(p)
? p
: path.join(tsconfigRootDir || process.cwd(), p);
}