Skip to content

Commit

Permalink
feat: Add --gitignore flag to read in .gitignore files (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Jun 12, 2024
1 parent 19a755b commit 3d9f7ce
Show file tree
Hide file tree
Showing 19 changed files with 224 additions and 50 deletions.
10 changes: 10 additions & 0 deletions packages/migrate-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ npx @eslint/migrate-config .eslintrc.json --commonjs
bunx @eslint/migrate-config .eslintrc.json --commonjs
```

### Including a `.gitignore` file

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:

```shell
npx @eslint/migrate-config .eslintrc.json --gitignore
# or
bunx @eslint/migrate-config .eslintrc.json --gitignore
```

## Followup Steps

Once you have completed the migration, you may need to manually modify the resulting config file.
Expand Down
7 changes: 2 additions & 5 deletions packages/migrate-config/src/migrate-config-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,7 @@ if (!configFilePath) {

const config = loadConfigFile(path.resolve(configFilePath));
const ignorePatterns = await loadIgnoreFile(
path.resolve(
configFilePath,
"../",
gitignore ? ".gitignore" : ".eslintignore",
),
path.resolve(configFilePath, "../", ".eslintignore"),
);
const resultExtname = commonjs ? "cjs" : "mjs";
const configFileExtname = path.extname(configFilePath);
Expand Down Expand Up @@ -107,6 +103,7 @@ if (ignorePatterns) {

const result = migrateConfig(config, {
sourceType: commonjs ? "commonjs" : "module",
gitignore,
});
await fsp.writeFile(resultFilePath, result.code);

Expand Down
132 changes: 103 additions & 29 deletions packages/migrate-config/src/migrate-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { convertIgnorePatternToMinimatch } from "@eslint/compat";
/** @typedef {import("eslint").Linter.ConfigOverride} ConfigOverride */
/** @typedef {import("recast").types.namedTypes.ObjectExpression} ObjectExpression */
/** @typedef {import("recast").types.namedTypes.ArrayExpression} ArrayExpression */
/** @typedef {import("recast").types.namedTypes.CallExpression} CallExpression */
/** @typedef {import("recast").types.namedTypes.Property} Property */
/** @typedef {import("recast").types.namedTypes.MemberExpression} MemberExpression */
/** @typedef {import("recast").types.namedTypes.Program} Program */
Expand Down Expand Up @@ -66,6 +67,12 @@ class Migration {
*/
messages = [];

/**
* Whether or not the migration needs the `__dirname` variable defined.
* @type {boolean}
*/
needsDirname = false;

/**
* Any initialization needed in the file.
* @type {Array<Statement>}
Expand Down Expand Up @@ -162,43 +169,53 @@ function getPluginVariableName(pluginName) {
}

/**
* Creates an initialization block for the FlatCompat utility.
* @param {"module"|"commonjs"} sourceType The module type to use.
* Get the initialization code for `__dirname`.
* @returns {Array<Statement>} The AST for the initialization block.
*/
function getFlatCompatInit(sourceType) {
let init = `
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
`;

function getDirnameInit() {
/*
* Need to calculate `__dirname` and `__filename` for ESM. Note that Recast
* doesn't support `import.meta.url`, so using an uppercase "I" to allow for
* parsing. We then need to replace it with the lowercase "i".
* Recast doesn't support `import.meta.url`, so using an uppercase "I" to
* allow for parsing. We then need to replace it with the lowercase "i".
*/
if (sourceType === "module") {
init = `
const init = `\n
const __filename = fileURLToPath(Import.meta.url);
const __dirname = path.dirname(__filename);
${init}`;
}
const __dirname = path.dirname(__filename);`;

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

// Replace uppercase "I" with lowercase "i" in "Import.meta.url"
if (sourceType === "module") {
result[0].declarations[0].init.arguments[0].object.object.name =
"import";
}
result[0].declarations[0].init.arguments[0].object.object.name = "import";

return result;
}

/**
* Creates an initialization block for the FlatCompat utility.
* @returns {Array<Statement>} The AST for the initialization block.
*/
function getFlatCompatInit() {
const init = `
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
`;
return recast.parse(init).program.body;
}

/**
* Creates an initialization block for the gitignore file.
* @returns {Statement} The AST for the initialization block.
*/
function getGitignoreInit() {
const init = `
const gitignorePath = path.resolve(__dirname, ".gitignore");
`;

return recast.parse(init).program.body[0];
}

/**
* Converts a glob pattern to a format that can be used in a flat config.
* @param {string} pattern The glob pattern to convert.
Expand All @@ -216,6 +233,37 @@ function convertGlobPattern(pattern) {
return `${isNegated ? "!" : ""}**/${patternToTest}`;
}

/**
* Creates the entry for the gitignore inclusion.
* @param {Migration} migration The migration object.
* @returns {CallExpression} The AST for the gitignore entry.
*/
function createGitignoreEntry(migration) {
migration.inits.push(getGitignoreInit());

if (!migration.imports.has("@eslint/compat")) {
migration.imports.set("@eslint/compat", {
bindings: ["includeIgnoreFile"],
added: true,
});
} else {
migration.imports
.get("@eslint/compat")
.bindings.push("includeIgnoreFile");
}

if (!migration.imports.has("node:path")) {
migration.imports.set("node:path", {
name: "path",
added: true,
});
}

const code = `includeIgnoreFile(gitignorePath)`;

return recast.parse(code).program.body[0].expression;
}

/**
* Creates the globals object from the config.
* @param {Config} config The config to create globals from.
Expand Down Expand Up @@ -786,18 +834,25 @@ function migrateConfigObject(migration, config) {
* @param {Config} config The eslintrc config to migrate.
* @param {Object} [options] Options for the migration.
* @param {"module"|"commonjs"} [options.sourceType] The module type to use.
* @param {boolean} [options.gitignore] `true` to include contents of a .gitignore file.
* @returns {{code:string,messages:Array<string>,imports:Map<string,MigrationImport>}} The migrated config and
* any messages to display to the user.
*/
export function migrateConfig(config, { sourceType = "module" } = {}) {
export function migrateConfig(
config,
{ sourceType = "module", gitignore = false } = {},
) {
const migration = new Migration(config);
const body = [];

/** @type {Array<CallExpression|ObjectExpression|SpreadElement>} */
const configArrayElements = [
...migrateConfigObject(
migration,
/** @type {ConfigOverride} */ (config),
),
];
const isModule = sourceType === "module";

// if the base config has no properties, then remove the empty object
if (
Expand All @@ -821,7 +876,7 @@ export function migrateConfig(config, { sourceType = "module" } = {}) {
config.extends ||
config.overrides?.some(override => override.extends)
) {
if (sourceType === "module") {
if (isModule) {
migration.imports.set("node:path", {
name: "path",
added: true,
Expand All @@ -839,15 +894,29 @@ export function migrateConfig(config, { sourceType = "module" } = {}) {
bindings: ["FlatCompat"],
added: true,
});
migration.inits.push(...getFlatCompatInit(sourceType));
migration.needsDirname ||= isModule;
migration.inits.push(...getFlatCompatInit());
}

// add .gitignore if necessary
if (gitignore) {
migration.needsDirname ||= isModule;
configArrayElements.unshift(createGitignoreEntry(migration));

if (migration.needsDirname && !migration.imports.has("node:url")) {
migration.imports.set("node:url", {
bindings: ["fileURLToPath"],
added: true,
});
}
}

if (config.ignorePatterns) {
configArrayElements.unshift(createGlobalIgnores(config));
}

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

// add calculation of `__dirname` if needed
if (migration.needsDirname) {
body.push(...getDirnameInit());
}

// output any inits
body.push(...migration.inits);

// output the actual config array to the program
if (sourceType === "commonjs") {
if (!isModule) {
body.push(
b.expressionStatement(
b.assignmentExpression(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ignorePatterns": ["baz"],
"extends": ["eslint:recommended"],
"rules": {
"no-console": "off"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
foo/bar
*.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const js = require("@eslint/js");

const {
FlatCompat,
} = require("@eslint/eslintrc");

const {
includeIgnoreFile,
} = require("@eslint/compat");

const path = require("node:path");
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
const gitignorePath = path.resolve(__dirname, ".gitignore");

module.exports = [{
ignores: ["**/baz"],
}, includeIgnoreFile(gitignorePath), ...compat.extends("eslint:recommended"), {
rules: {
"no-console": "off",
},
}];
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import { includeIgnoreFile } from "@eslint/compat";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
const gitignorePath = path.resolve(__dirname, ".gitignore");

export default [{
ignores: ["**/baz"],
}, includeIgnoreFile(gitignorePath), ...compat.extends("eslint:recommended"), {
rules: {
"no-console": "off",
},
}];
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"no-console": "off"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
foo/bar
*.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const {
includeIgnoreFile,
} = require("@eslint/compat");

const path = require("node:path");
const gitignorePath = path.resolve(__dirname, ".gitignore");

module.exports = [includeIgnoreFile(gitignorePath), {
rules: {
"no-console": "off",
},
}];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { includeIgnoreFile } from "@eslint/compat";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, ".gitignore");

export default [includeIgnoreFile(gitignorePath), {
rules: {
"no-console": "off",
},
}];
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
Expand Down
Loading

0 comments on commit 3d9f7ce

Please sign in to comment.