Skip to content
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

feat: Add --gitignore flag to read in .gitignore files #55

Merged
merged 5 commits into from
Jun 12, 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
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));
nzakas marked this conversation as resolved.
Show resolved Hide resolved

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);
nzakas marked this conversation as resolved.
Show resolved Hide resolved
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