Skip to content

Commit 3d9f7ce

Browse files
authored
feat: Add --gitignore flag to read in .gitignore files (#55)
1 parent 19a755b commit 3d9f7ce

File tree

19 files changed

+224
-50
lines changed

19 files changed

+224
-50
lines changed

packages/migrate-config/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ npx @eslint/migrate-config .eslintrc.json --commonjs
3838
bunx @eslint/migrate-config .eslintrc.json --commonjs
3939
```
4040

41+
### Including a `.gitignore` file
42+
43+
If you are currently using `--ignore-path .gitignore` on the CLI, you'll need to read the `.gitignore` file into your config file. The migration can handle this for you by passing the `--gitignore` flag:
44+
45+
```shell
46+
npx @eslint/migrate-config .eslintrc.json --gitignore
47+
# or
48+
bunx @eslint/migrate-config .eslintrc.json --gitignore
49+
```
50+
4151
## Followup Steps
4252

4353
Once you have completed the migration, you may need to manually modify the resulting config file.

packages/migrate-config/src/migrate-config-cli.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,7 @@ if (!configFilePath) {
6666

6767
const config = loadConfigFile(path.resolve(configFilePath));
6868
const ignorePatterns = await loadIgnoreFile(
69-
path.resolve(
70-
configFilePath,
71-
"../",
72-
gitignore ? ".gitignore" : ".eslintignore",
73-
),
69+
path.resolve(configFilePath, "../", ".eslintignore"),
7470
);
7571
const resultExtname = commonjs ? "cjs" : "mjs";
7672
const configFileExtname = path.extname(configFilePath);
@@ -107,6 +103,7 @@ if (ignorePatterns) {
107103

108104
const result = migrateConfig(config, {
109105
sourceType: commonjs ? "commonjs" : "module",
106+
gitignore,
110107
});
111108
await fsp.writeFile(resultFilePath, result.code);
112109

packages/migrate-config/src/migrate-config.js

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { convertIgnorePatternToMinimatch } from "@eslint/compat";
2222
/** @typedef {import("eslint").Linter.ConfigOverride} ConfigOverride */
2323
/** @typedef {import("recast").types.namedTypes.ObjectExpression} ObjectExpression */
2424
/** @typedef {import("recast").types.namedTypes.ArrayExpression} ArrayExpression */
25+
/** @typedef {import("recast").types.namedTypes.CallExpression} CallExpression */
2526
/** @typedef {import("recast").types.namedTypes.Property} Property */
2627
/** @typedef {import("recast").types.namedTypes.MemberExpression} MemberExpression */
2728
/** @typedef {import("recast").types.namedTypes.Program} Program */
@@ -66,6 +67,12 @@ class Migration {
6667
*/
6768
messages = [];
6869

70+
/**
71+
* Whether or not the migration needs the `__dirname` variable defined.
72+
* @type {boolean}
73+
*/
74+
needsDirname = false;
75+
6976
/**
7077
* Any initialization needed in the file.
7178
* @type {Array<Statement>}
@@ -162,43 +169,53 @@ function getPluginVariableName(pluginName) {
162169
}
163170

164171
/**
165-
* Creates an initialization block for the FlatCompat utility.
166-
* @param {"module"|"commonjs"} sourceType The module type to use.
172+
* Get the initialization code for `__dirname`.
167173
* @returns {Array<Statement>} The AST for the initialization block.
168174
*/
169-
function getFlatCompatInit(sourceType) {
170-
let init = `
171-
const compat = new FlatCompat({
172-
baseDirectory: __dirname,
173-
recommendedConfig: js.configs.recommended,
174-
allConfig: js.configs.all
175-
});
176-
`;
177-
175+
function getDirnameInit() {
178176
/*
179-
* Need to calculate `__dirname` and `__filename` for ESM. Note that Recast
180-
* doesn't support `import.meta.url`, so using an uppercase "I" to allow for
181-
* parsing. We then need to replace it with the lowercase "i".
177+
* Recast doesn't support `import.meta.url`, so using an uppercase "I" to
178+
* allow for parsing. We then need to replace it with the lowercase "i".
182179
*/
183-
if (sourceType === "module") {
184-
init = `
180+
const init = `\n
185181
const __filename = fileURLToPath(Import.meta.url);
186-
const __dirname = path.dirname(__filename);
187-
188-
${init}`;
189-
}
182+
const __dirname = path.dirname(__filename);`;
190183

191184
const result = recast.parse(init).program.body;
192185

193186
// Replace uppercase "I" with lowercase "i" in "Import.meta.url"
194-
if (sourceType === "module") {
195-
result[0].declarations[0].init.arguments[0].object.object.name =
196-
"import";
197-
}
187+
result[0].declarations[0].init.arguments[0].object.object.name = "import";
198188

199189
return result;
200190
}
201191

192+
/**
193+
* Creates an initialization block for the FlatCompat utility.
194+
* @returns {Array<Statement>} The AST for the initialization block.
195+
*/
196+
function getFlatCompatInit() {
197+
const init = `
198+
const compat = new FlatCompat({
199+
baseDirectory: __dirname,
200+
recommendedConfig: js.configs.recommended,
201+
allConfig: js.configs.all
202+
});
203+
`;
204+
return recast.parse(init).program.body;
205+
}
206+
207+
/**
208+
* Creates an initialization block for the gitignore file.
209+
* @returns {Statement} The AST for the initialization block.
210+
*/
211+
function getGitignoreInit() {
212+
const init = `
213+
const gitignorePath = path.resolve(__dirname, ".gitignore");
214+
`;
215+
216+
return recast.parse(init).program.body[0];
217+
}
218+
202219
/**
203220
* Converts a glob pattern to a format that can be used in a flat config.
204221
* @param {string} pattern The glob pattern to convert.
@@ -216,6 +233,37 @@ function convertGlobPattern(pattern) {
216233
return `${isNegated ? "!" : ""}**/${patternToTest}`;
217234
}
218235

236+
/**
237+
* Creates the entry for the gitignore inclusion.
238+
* @param {Migration} migration The migration object.
239+
* @returns {CallExpression} The AST for the gitignore entry.
240+
*/
241+
function createGitignoreEntry(migration) {
242+
migration.inits.push(getGitignoreInit());
243+
244+
if (!migration.imports.has("@eslint/compat")) {
245+
migration.imports.set("@eslint/compat", {
246+
bindings: ["includeIgnoreFile"],
247+
added: true,
248+
});
249+
} else {
250+
migration.imports
251+
.get("@eslint/compat")
252+
.bindings.push("includeIgnoreFile");
253+
}
254+
255+
if (!migration.imports.has("node:path")) {
256+
migration.imports.set("node:path", {
257+
name: "path",
258+
added: true,
259+
});
260+
}
261+
262+
const code = `includeIgnoreFile(gitignorePath)`;
263+
264+
return recast.parse(code).program.body[0].expression;
265+
}
266+
219267
/**
220268
* Creates the globals object from the config.
221269
* @param {Config} config The config to create globals from.
@@ -786,18 +834,25 @@ function migrateConfigObject(migration, config) {
786834
* @param {Config} config The eslintrc config to migrate.
787835
* @param {Object} [options] Options for the migration.
788836
* @param {"module"|"commonjs"} [options.sourceType] The module type to use.
837+
* @param {boolean} [options.gitignore] `true` to include contents of a .gitignore file.
789838
* @returns {{code:string,messages:Array<string>,imports:Map<string,MigrationImport>}} The migrated config and
790839
* any messages to display to the user.
791840
*/
792-
export function migrateConfig(config, { sourceType = "module" } = {}) {
841+
export function migrateConfig(
842+
config,
843+
{ sourceType = "module", gitignore = false } = {},
844+
) {
793845
const migration = new Migration(config);
794846
const body = [];
847+
848+
/** @type {Array<CallExpression|ObjectExpression|SpreadElement>} */
795849
const configArrayElements = [
796850
...migrateConfigObject(
797851
migration,
798852
/** @type {ConfigOverride} */ (config),
799853
),
800854
];
855+
const isModule = sourceType === "module";
801856

802857
// if the base config has no properties, then remove the empty object
803858
if (
@@ -821,7 +876,7 @@ export function migrateConfig(config, { sourceType = "module" } = {}) {
821876
config.extends ||
822877
config.overrides?.some(override => override.extends)
823878
) {
824-
if (sourceType === "module") {
879+
if (isModule) {
825880
migration.imports.set("node:path", {
826881
name: "path",
827882
added: true,
@@ -839,15 +894,29 @@ export function migrateConfig(config, { sourceType = "module" } = {}) {
839894
bindings: ["FlatCompat"],
840895
added: true,
841896
});
842-
migration.inits.push(...getFlatCompatInit(sourceType));
897+
migration.needsDirname ||= isModule;
898+
migration.inits.push(...getFlatCompatInit());
899+
}
900+
901+
// add .gitignore if necessary
902+
if (gitignore) {
903+
migration.needsDirname ||= isModule;
904+
configArrayElements.unshift(createGitignoreEntry(migration));
905+
906+
if (migration.needsDirname && !migration.imports.has("node:url")) {
907+
migration.imports.set("node:url", {
908+
bindings: ["fileURLToPath"],
909+
added: true,
910+
});
911+
}
843912
}
844913

845914
if (config.ignorePatterns) {
846915
configArrayElements.unshift(createGlobalIgnores(config));
847916
}
848917

849918
// add imports to the top of the file
850-
if (sourceType === "commonjs") {
919+
if (!isModule) {
851920
migration.imports.forEach(({ name, bindings }, path) => {
852921
const bindingProperties = bindings?.map(binding => {
853922
const bindingProperty = b.property(
@@ -897,11 +966,16 @@ export function migrateConfig(config, { sourceType = "module" } = {}) {
897966
});
898967
}
899968

969+
// add calculation of `__dirname` if needed
970+
if (migration.needsDirname) {
971+
body.push(...getDirnameInit());
972+
}
973+
900974
// output any inits
901975
body.push(...migration.inits);
902976

903977
// output the actual config array to the program
904-
if (sourceType === "commonjs") {
978+
if (!isModule) {
905979
body.push(
906980
b.expressionStatement(
907981
b.assignmentExpression(

packages/migrate-config/tests/fixtures/basic-eslintrc/expected.mjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ import path from "node:path";
1111
import { fileURLToPath } from "node:url";
1212
import js from "@eslint/js";
1313
import { FlatCompat } from "@eslint/eslintrc";
14+
1415
const __filename = fileURLToPath(import.meta.url);
1516
const __dirname = path.dirname(__filename);
16-
17-
1817
const compat = new FlatCompat({
1918
baseDirectory: __dirname,
2019
recommendedConfig: js.configs.recommended,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ignorePatterns": ["baz"],
3+
"extends": ["eslint:recommended"],
4+
"rules": {
5+
"no-console": "off"
6+
}
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist
2+
foo/bar
3+
*.log
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const js = require("@eslint/js");
2+
3+
const {
4+
FlatCompat,
5+
} = require("@eslint/eslintrc");
6+
7+
const {
8+
includeIgnoreFile,
9+
} = require("@eslint/compat");
10+
11+
const path = require("node:path");
12+
const compat = new FlatCompat({
13+
baseDirectory: __dirname,
14+
recommendedConfig: js.configs.recommended,
15+
allConfig: js.configs.all
16+
});
17+
const gitignorePath = path.resolve(__dirname, ".gitignore");
18+
19+
module.exports = [{
20+
ignores: ["**/baz"],
21+
}, includeIgnoreFile(gitignorePath), ...compat.extends("eslint:recommended"), {
22+
rules: {
23+
"no-console": "off",
24+
},
25+
}];
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import path from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
import js from "@eslint/js";
4+
import { FlatCompat } from "@eslint/eslintrc";
5+
import { includeIgnoreFile } from "@eslint/compat";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const compat = new FlatCompat({
10+
baseDirectory: __dirname,
11+
recommendedConfig: js.configs.recommended,
12+
allConfig: js.configs.all
13+
});
14+
const gitignorePath = path.resolve(__dirname, ".gitignore");
15+
16+
export default [{
17+
ignores: ["**/baz"],
18+
}, includeIgnoreFile(gitignorePath), ...compat.extends("eslint:recommended"), {
19+
rules: {
20+
"no-console": "off",
21+
},
22+
}];
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"rules": {
3+
"no-console": "off"
4+
}
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist
2+
foo/bar
3+
*.log

0 commit comments

Comments
 (0)