feat(integrations, project): new options for eslint integration
MODIFY
internal/core/common/IntegrationLoader.ts
internal/core/common/IntegrationLoader.ts
@@ -21,6 +21,7 @@ import {
} from "@internal/diagnostics";
import internalModule = require("module");
+import {ProjectConfigIntegrations} from "@internal/project";
const requires: AbsoluteFilePathMap<NodeRequire> = new AbsoluteFilePathMap();
@@ -30,30 +31,37 @@ export function getRequire(path: AbsoluteFilePath): NodeRequire {
return existing;
}
- const require: NodeRequire = internalModule.createRequire
- ? internalModule.createRequire(path.join())
- : internalModule.createRequireFromPath(path.join());
+ const require: NodeRequire = internalModule.createRequire(path.join());
requires.set(path, require);
return require;
}
-type IntegrationLoaderNormalize<Value> = (
- consumer: Consumer,
- path: AbsoluteFilePath,
- version: undefined | SemverVersion,
-) => Value;
+type IntegrationLoaderNormalizePayload<IntegrationName extends keyof ProjectConfigIntegrations> = {
+ consumer: Consumer;
+ cwd: AbsoluteFilePath;
+ version: undefined | SemverVersion;
+ opts?: Omit<ProjectConfigIntegrations[IntegrationName], "enabled">;
+};
+
+type IntegrationLoaderNormalize<
+ Value,
+ IntegrationName extends keyof ProjectConfigIntegrations
+> = (payload: IntegrationLoaderNormalizePayload<IntegrationName>) => Value;
type IntegrationLoaderEntry<Value> = {
version: undefined | SemverVersion;
module: Value;
};
-export default class IntegrationLoader<Value> {
+export default class IntegrationLoader<
+ Value,
+ IntegrationName extends keyof ProjectConfigIntegrations
+> {
constructor(
{name, range, normalize}: {
name: string;
range?: string;
- normalize: IntegrationLoaderNormalize<Value>;
+ normalize: IntegrationLoaderNormalize<Value, IntegrationName>;
},
) {
this.loaded = new AbsoluteFilePathMap();
@@ -64,7 +72,7 @@ export default class IntegrationLoader<Value> {
}
private loaded: AbsoluteFilePathMap<IntegrationLoaderEntry<Value>>;
- private normalize: IntegrationLoaderNormalize<Value>;
+ private normalize: IntegrationLoaderNormalize<Value, IntegrationName>;
private name: string;
private range: undefined | SemverRange;
@@ -107,6 +115,7 @@ export default class IntegrationLoader<Value> {
public async load(
path: AbsoluteFilePath,
cwd: AbsoluteFilePath,
+ opts?: Omit<ProjectConfigIntegrations[IntegrationName], "enabled">,
): Promise<IntegrationLoaderEntry<Value>> {
const existing = this.loaded.get(path);
if (existing !== undefined) {
@@ -163,7 +172,7 @@ export default class IntegrationLoader<Value> {
DIAGNOSTIC_CATEGORIES["integration/load"],
this.name,
);
- const module = this.normalize(consumer, cwd, version);
+ const module = this.normalize({consumer, cwd, version, opts});
const entry: IntegrationLoaderEntry<Value> = {version, module};
this.loaded.set(path, entry);

MODIFY
internal/core/worker/integrations/eslint.ts
internal/core/worker/integrations/eslint.ts
@@ -6,20 +6,22 @@ import {Duration, DurationMeasurer} from "@internal/numbers";
import {Position} from "@internal/parser-core";
import {WorkerProject} from "../types";
import Worker from "../Worker";
+import {Consumer} from "@internal/consume";
-const eslintLoader = new IntegrationLoader({
+const eslintLoader = new IntegrationLoader<Consumer, "eslint">({
name: "eslint",
range: "^7.0.0",
- normalize: (consumer, cwd) => {
+ normalize: ({consumer, cwd, opts}) => {
const Factory = consumer.get("ESLint").asFunction();
const eslint = Reflect.construct(
Factory,
[
{
cwd: cwd.join(),
- globInputPaths: false,
- fix: true,
- rulePaths: [],
+ fix: opts?.fix,
+ rulePaths: opts?.rulePaths,
+ extensions: opts?.extensions,
+ globInputPaths: opts?.globInputPaths,
},
],
);
@@ -49,7 +51,11 @@ export async function maybeRunESLint(
const diagnostics: Diagnostic[] = [];
- const loader = await eslintLoader.load(project.configPath, project.directory);
+ const loader = await eslintLoader.load(
+ project.configPath,
+ project.directory,
+ project.config.integrations.eslint,
+ );
const ignored = await eslintLoader.wrap(async () => {
const isPathIgnored = loader.module.get("isPathIgnored").asWrappedFunction();

MODIFY
internal/core/worker/integrations/prettier.ts
internal/core/worker/integrations/prettier.ts
@@ -3,11 +3,12 @@ import Worker from "../Worker";
import {FileReference, WorkerProject} from "@internal/core";
import {Duration, DurationMeasurer} from "@internal/numbers";
import {Diagnostic} from "@internal/diagnostics";
+import {Consumer} from "@internal/consume";
-const prettierLoader = new IntegrationLoader({
+const prettierLoader = new IntegrationLoader<Consumer, "prettier">({
name: "prettier",
range: "^2.0.0",
- normalize: (consumer) => {
+ normalize: ({consumer}) => {
return consumer;
},
});

MODIFY
internal/project/constants.ts
internal/project/constants.ts
@@ -8,10 +8,6 @@ import {CONFIG_EXTENSIONS} from "@internal/codec-config";
export const VCS_IGNORE_FILENAMES = [".gitignore", ".hgignore"];
export const ESLINT_CONFIG_FILENAMES: string[] = [
- ".eslintrc.js",
- ".eslintrc.cjs",
- ".eslintrc.yaml",
- ".eslintrc.yml",
".eslintrc.json",
".eslintignore",
];

ADD
internal/project/integrations/loadEslint.ts
+import {consumeUnknown} from "@internal/consume";
+import {DIAGNOSTIC_CATEGORIES} from "@internal/diagnostics";
+import {IntegrationEslintConfig, arrayOfStrings} from "@internal/project";
+import {json} from "@internal/codec-config";
+
+export function loadEslint(file: string): Partial<IntegrationEslintConfig> {
+ const config: Partial<IntegrationEslintConfig> = {};
+ const data = json.parse({input: file});
+ const eslint = consumeUnknown(data, DIAGNOSTIC_CATEGORIES.eslint, "json");
+
+ if (eslint.has("globInputPaths")) {
+ config.globInputPaths = eslint.get("globInputPaths").asBoolean();
+ }
+ if (eslint.has("extensions")) {
+ config.extensions = arrayOfStrings(eslint.get("extensions"));
+ }
+ if (eslint.has("fix")) {
+ config.fix = eslint.get("fix").asBoolean();
+ }
+ if (eslint.has("rulePaths")) {
+ config.rulePaths = arrayOfStrings(eslint.get("rulePaths"));
+ }
+
+ return config;
+}

MODIFY
internal/project/integrations/loadPrettier.ts
internal/project/integrations/loadPrettier.ts
import {consumeUnknown} from "@internal/consume";
import {DIAGNOSTIC_CATEGORIES} from "@internal/diagnostics";
-import {PrettierConfig} from "@internal/project";
+import {IntegrationPrettierConfig} from "@internal/project";
import {json, json5} from "@internal/codec-config";
export function loadPrettier(
file: string,
extension: string,
-): Partial<PrettierConfig> {
- const config: Partial<PrettierConfig> = {};
+): Partial<IntegrationPrettierConfig> {
+ const config: Partial<IntegrationPrettierConfig> = {};
let data: unknown;
// NOTE: we only support json for now
if (extension === "json5") {

MODIFY
internal/project/load.ts
internal/project/load.ts
@@ -31,6 +31,7 @@ import {CachedFileReader} from "@internal/fs";
import {parseSemverRange} from "@internal/codec-semver";
import {descriptions} from "@internal/diagnostics";
import {
+ ESLINT_CONFIG_FILENAMES,
INTEGRATIONS_IGNORE_FILES,
PRETTIER_CONFIG_FILESNAMES,
PROJECT_CONFIG_PACKAGE_JSON_FIELD,
@@ -41,6 +42,7 @@ import {sha256} from "@internal/string-utils";
import {resolveBrowsers} from "@internal/codec-browsers";
import {ParserOptions} from "@internal/parser-core";
import {loadPrettier} from "@internal/project/integrations/loadPrettier";
+import {loadEslint} from "@internal/project/integrations/loadEslint";
type NormalizedPartial = {
partial: PartialProjectConfig;
@@ -131,6 +133,20 @@ export async function loadCompleteProjectConfig(
}
}
+ // Load extensions configuration of eslint
+ for (const eslintFile of ESLINT_CONFIG_FILENAMES) {
+ const possiblePath = projectDirectory.append(eslintFile);
+ meta.configDependencies.add(possiblePath);
+ if (await possiblePath.exists()) {
+ const file = await reader.readFileText(possiblePath);
+ const eslintConfig = loadEslint(file);
+ config.integrations.eslint = {
+ ...config.integrations.eslint,
+ ...eslintConfig,
+ };
+ }
+ }
+
// read ignore files
for (const fileToIgnore of INTEGRATIONS_IGNORE_FILES) {
const possiblePath = projectDirectory.append(fileToIgnore);

MODIFY
internal/project/types.ts
internal/project/types.ts
@@ -103,7 +103,7 @@ export type ProjectConfigObjects = {
targets: Map<string, GetBrowserProps[]>;
};
-export type PrettierConfig = {
+export type IntegrationPrettierConfig = {
printWidth: number;
tabWidth: number;
useTabs: boolean;
@@ -111,10 +111,17 @@ export type PrettierConfig = {
singleQuote: boolean;
};
+export type IntegrationEslintConfig = {
+ fix: boolean;
+ extensions: string[];
+ rulePaths: string[];
+ globInputPaths: boolean;
+};
+
export type ProjectConfigIntegrations = {
- eslint: Enableable;
+ eslint: Enableable & Partial<IntegrationEslintConfig>;
typescriptChecker: Enableable;
- prettier: Enableable & Partial<PrettierConfig>;
+ prettier: Enableable & Partial<IntegrationPrettierConfig>;
};
export type ProjectConfigCategoriesWithIgnore = "tests" | "lint";
@@ -223,7 +230,7 @@ export type RawUserProjectConfig = DeepPartial<{
integrations: {
eslint: Enableable;
typescriptChecker: Enableable;
- prettier: Enableable & Partial<PrettierConfig>;
+ prettier: Enableable & Partial<IntegrationPrettierConfig>;
};
}>;

MODIFY
website/src/_includes/docs/project-config.md
website/src/_includes/docs/project-config.md
@@ -134,11 +134,11 @@ If your project uses `.prettierrc` already, Rome will load the configuration and
Alternatively, you can configure `prettier` inside Rome's configuration. Rome supports only a
subset of options:
-- [printWidth](https://prettier.io/docs/en/options.html#print-width);
-- [tabWidth](https://prettier.io/docs/en/options.html#tab-width);
-- [useTabs](https://prettier.io/docs/en/options.html#tabs);
-- [semi](https://prettier.io/docs/en/options.html#semicolons);
-- [singleQuote](https://prettier.io/docs/en/options.html#quotes);
+- [`printWidth`](https://prettier.io/docs/en/options.html#print-width);
+- [`tabWidth`](https://prettier.io/docs/en/options.html#tab-width);
+- [`useTabs`](https://prettier.io/docs/en/options.html#tabs);
+- [`semi`](https://prettier.io/docs/en/options.html#semicolons);
+- [`singleQuote`](https://prettier.io/docs/en/options.html#quotes);
The rest of options will be ignored and won't be passed to `prettier`.
@@ -169,7 +169,56 @@ Ultimately, the configuration will look like this:
### `integrations.eslint`
-MISSING DOCUMENTATION
+You can use Rome to lint your code using eslint. In order to integrate it,
+you have to install `eslint` in your project and enabled the integration via Rome:
+
+```bash
+rome config enable integrations.eslint
+```
+
+> The minimum `eslint`'s version supported is `7.0.0`;
+
+Now, when you run `./rome check --apply`, Rome will use `prettier` to format your code.
+
+If your project uses `.eslint.json` already, Rome will load the configuration and pass it to
+`eslint`.
+
+Alternatively, you can configure `eslint` inside Rome's configuration. Rome supports only a
+subset of options:
+
+- `fix`
+- `extensions`
+- `globInputPaths`
+- `rulePaths`
+
+You can find more information in the [eslint webpage](https://eslint.org/docs/developer-guide/nodejs-api#parameters).
+
+The rest of options will be ignored and won't be passed to `eslint`.
+
+Ultimately, the configuration will look like this:
+
+```json
+{
+ "rome": {
+ "root": true,
+ "name": "fancy-project",
+ "lint": {
+ "enabled": false
+ },
+ "format": {
+ "enabled": false
+ },
+ "integrations": {
+ "eslint": {
+ "fix": true,
+ "extensions": [".js", ".ts", ".jsx"],
+ "globInputPaths": false,
+ "rulePaths": ["./path/to/rules", "../../path/to/other/rules"]
+ }
+ }
+ }
+}
+```
### Supported Locations

feat(integrations, project): new options for eslint integration (#1580)