Skip to content

Commit

Permalink
Use ESLint flat configs
Browse files Browse the repository at this point in the history
  • Loading branch information
onigoetz committed Apr 10, 2024
1 parent 5f3c213 commit 37d8844
Show file tree
Hide file tree
Showing 36 changed files with 562 additions and 437 deletions.
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
};

0 comments on commit 37d8844

Please sign in to comment.