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

Use ESLint flat configs #2250

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ packages/integration/fixtures/**/.gitignore
packages/integration/fixtures/**/stylelint.config.mjs
packages/integration/fixtures/**/prettier.config.mjs
packages/integration/fixtures/**/jest.config.mjs
packages/integration/fixtures/**/eslint.config.js

# Caches
.rpt2_cache
Expand Down
8 changes: 5 additions & 3 deletions packages/crafty-preset-babel/src/gulp.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ module.exports = function createTask(crafty, bundle, StreamHandler) {
// Linting
if (!crafty.isWatching()) {
const {
toTempFile
} = require("@swissquote/crafty-preset-eslint/src/eslintConfigurator");
toTempFile,
toolConfiguration
} = require("@swissquote/crafty-preset-eslint/src/eslintConfigurator.js");
const eslint = require("@swissquote/crafty-commons-gulp/packages/gulp-eslint-new");
stream
.add(
eslint({
overrideConfigFile: toTempFile(crafty.config.eslint)
configType: "flat",
overrideConfigFile: toTempFile(toolConfiguration(crafty))
})
)
.add(eslint.format())
Expand Down
10 changes: 5 additions & 5 deletions packages/crafty-preset-eslint/src/commands/jsLint.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env node

const { configurationBuilder, toTempFile } = require("../eslintConfigurator");
// Force flat config mode
process.env.ESLINT_USE_FLAT_CONFIG = "true";

const configuration = configurationBuilder(process.argv);
const { toTempFile, jsLintConfiguration } = require("../eslintConfigurator.js");

process.argv = configuration.args;

const tmpfile = toTempFile(configuration.configuration);
// Write config to a file
const tmpfile = toTempFile(jsLintConfiguration());

// Remove "jsLint" from command
// Doesn't apply when we use the command as if we used eslint
Expand Down
271 changes: 193 additions & 78 deletions packages/crafty-preset-eslint/src/eslintConfigurator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,114 +3,229 @@ const path = require("path");
const resolveFrom = require("../packages/resolve-from");
const tmp = require("@swissquote/crafty-commons/packages/tmp");

function mergeConfiguration(configuration, args, newConfiguration) {
if (
newConfiguration.useEslintrc === false &&
args.indexOf("--no-eslintrc") === -1
) {
args.push("--no-eslintrc");
}
/**
* Extract all configuration from "crafty jsLint" that won't be understood by `eslint`
*
* @param {*} args
* @returns
*/
function extractConfig(args) {
const presets = [];
let configFile;

if (newConfiguration.extends) {
if (typeof newConfiguration.extends === "string") {
configuration.extends.push(newConfiguration.extends);
} else {
newConfiguration.extends.forEach(item =>
configuration.extends.push(item)
);
}
}

Object.assign(configuration.rules, newConfiguration.rules || {});
let idx;
if (args.indexOf("--preset") > -1) {
while ((idx = args.indexOf("--preset")) > -1) {
presets.push(args[idx + 1]);

if (newConfiguration.baseConfig && newConfiguration.baseConfig.settings) {
Object.assign(configuration.settings, newConfiguration.baseConfig.settings);
args.splice(idx, 2);
}
} else {
presets.push("format");
}

if (newConfiguration.settings) {
Object.assign(configuration.settings, newConfiguration.settings);
if ((idx = args.indexOf("--config")) > -1) {
configFile = args[idx + 1];
args.splice(idx, 2);
}

if (newConfiguration.configFile) {
mergeConfiguration(
configuration,
args,
require(newConfiguration.configFile)
);
}
return {
presets,
configFile
};
}

function configurationBuilder(args) {
const configuration = {
plugins: ["@swissquote/swissquote"],
extends: ["plugin:@swissquote/swissquote/format"],
rules: {},
settings: {}
};
/**
* Plugins can be present only once
* Since we want to load the plugins only when needed we still want to declare them the closest to where they're needed.
* This step makes sure that plugins appear only once in the final configuration
*/
function deduplicatePlugins(configs) {
const seenPlugin = new Set();

configs.forEach((config, index) => {
if (!config.plugins) {
return;
}

// Override from default config if it exists
if (global.craftyConfig) {
mergeConfiguration(configuration, args, global.craftyConfig.eslint);
}
const pluginNames = Object.keys(config.plugins);

let hasDuplicates = false;
const newPluginMap = {};
for (const pluginName of pluginNames) {
if (seenPlugin.has(pluginName)) {
hasDuplicates = true;
} else {
seenPlugin.add(pluginName);
newPluginMap[pluginName] = config.plugins[pluginName];
}
}

// Merge configuration that can be passed in cli arguments
let idx;
if ((idx = args.indexOf("--config")) > -1) {
const configFile = args[idx + 1];
args.splice(idx, 2);
if (hasDuplicates) {
// Clone the original config in case it's used more than once
const cloned = { ...config };
cloned.plugins = newPluginMap;
configs[index] = cloned;
}
});
}

mergeConfiguration(
configuration,
args,
require(resolveFrom.silent(process.cwd(), configFile) ||
path.join(process.cwd(), configFile))
);
function toESLintConfig(crafty, config = {}) {
const configs = [];

// Load configuration presets
if (config.presets) {
for (const preset of config.presets) {
const subConfigs = require(`@swissquote/eslint-plugin-swissquote`)
.configs[preset];
configs.push(...subConfigs);
}
}

if (args.indexOf("--preset") > -1) {
configuration.extends = [];
// Override from default config if it exists
if (crafty?.config?.eslint) {
configs.push(...crafty.config.eslint);
}

while ((idx = args.indexOf("--preset")) > -1) {
configuration.extends.push(
`plugin:@swissquote/swissquote/${args[idx + 1]}`
// Load other configuration files
if (config.configFile) {
// TODO : handle ESM and async
const subConfigs = require(resolveFrom.silent(
process.cwd(),
config.configFile
) || path.join(process.cwd(), config.configFile));

if (!Array.isArray(subConfigs)) {
throw new Error(
`Expected ${
config.configFile
} to be an array of configuration but got ${typeof subConfigs}`
);
args.splice(idx, 2);
}

configs.push(...subConfigs);
}

// Disable `no-var` as this linter can also be run
// on es5 code, if used with --fix, the result
// would be broken code or false positives.
configuration.rules["no-var"] = 0;
// TODO :: apply only on `jsLint`
configs.push({
rules: {
// Disable `no-var` as this linter can also be run
// on es5 code, if used with --fix, the result
// would be broken code or false positives.
"no-var": 0
}
});

return {
configuration,
args
};
deduplicatePlugins(configs);

return configs;
}

function stringifyConfiguration(configuration) {
return `// This configuration was generated by Crafty
// This file is generated to improve IDE Integration
// You don't need to commit this file, nor need it to run \`crafty build\`
/*

√: Yes
~: Maybe but not mandatory
X: Should not

| Item | IDE | Tool | jsLint |
| --------------------------- | --- | ---- | ------ |
| Read `crafty.config.js` | √ | √ | X |
| Use Crafty defaults | √ | √ | X |
| Extra presets from CLI | X | X | √ |

*/

const ESLINT_PRESET_PATH = require.resolve("@swissquote/crafty-preset-eslint");
const CRAFTY_PATH = require.resolve("@swissquote/crafty");

/**
* IDE Configuration generation.
*
* @param {Crafty} crafty
* @returns
*/
function ideConfiguration(crafty) {
return `// AUTOGENERATED BY CRAFTY - DO NOT EDIT
// This file helps IDEs autoconfigure themselves
// any change here will only be used by your IDE, not by Crafty

/* global process */

module.exports = (async () => {
const { initialize } = await import("@swissquote/crafty");
const { toESLintConfig } = await import("@swissquote/crafty-preset-eslint");

const crafty = await initialize(process.argv, ${JSON.stringify(
crafty.presetsFromCli,
null,
2
)});

const config = toESLintConfig(crafty, {});

return config;
})();
`;
}

/**
* Configuration for other tools
*
* @param {Crafty} crafty
* @returns
*/
function toolConfiguration(crafty) {
return `// AUTOGENERATED BY CRAFTY - DO NOT EDIT

import { initialize } from "${CRAFTY_PATH}";
import { toESLintConfig } from "${ESLINT_PRESET_PATH}";

const crafty = await initialize(process.argv, ${JSON.stringify(
crafty.presetsFromCli,
null,
2
)});

const config = toESLintConfig(crafty, {});

export default config;
`;
}

/**
* Configuration for jsLint command
*
* @returns
*/
function jsLintConfiguration() {
const extraConfig = extractConfig(process.argv);

return `// AUTOGENERATED BY CRAFTY - DO NOT EDIT
import { initialize } from "${CRAFTY_PATH}";
import { toESLintConfig } from "${ESLINT_PRESET_PATH}";

const extraConfig = ${JSON.stringify(extraConfig, null, 2)};

const config = toESLintConfig(null, extraConfig);

// Fix module resolution
require(${JSON.stringify(require.resolve("./patchModuleResolver"))});
//console.log(JSON.stringify(config, null, 2));

module.exports = ${JSON.stringify(configuration, null, 4)};
export default config;
`;
}

function toTempFile(configuration) {
const tmpfile = tmp.fileSync({ postfix: ".js" }).name;
function toTempFile(content) {
const tmpfile = tmp.fileSync({ postfix: ".mjs" }).name;

fs.writeFileSync(tmpfile, stringifyConfiguration(configuration));
fs.writeFileSync(tmpfile, content);

return tmpfile;
}

module.exports = {
configurationBuilder,
stringifyConfiguration,
toTempFile
ideConfiguration,
toolConfiguration,
jsLintConfiguration,
toTempFile,
toESLintConfig
};