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(js-config): js config files (e.g. .eslintrc.js) #2414

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

andrestone
Copy link
Contributor

@andrestone andrestone commented Feb 4, 2023

Introduces JsConfigFile which is compatible with JsonFile interface.

The class exposes the static method JsConfigFile.identifier(str: string) to allow rendering globals such as __dirname.

  //...
  const eslint = new Eslint(project, {
    devdirs: ["foo", "bar"],
    dirs: ["mysrc"],
    jsConfig: true,
  });
  eslint.config.parserOptions = {
    project: ["./tsconfig.json"],
    tsconfigRootDir: JsConfigFile.identifier("__dirname"),
  };
  // ...

Implemented using a magic string, happy to reimplement if someone has a better idea.

Fixes #2405


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@andrestone andrestone changed the title feat(js-config): js config files (e.g. .eslintrc.json) feat(js-config): js config files (e.g. .eslintrc.js) Feb 4, 2023
@andrestone andrestone changed the title feat(js-config): js config files (e.g. .eslintrc.js) feat(js-config): js config files (e.g. .eslintrc.js) Feb 4, 2023
@codecov-commenter
Copy link

codecov-commenter commented Feb 4, 2023

Codecov Report

Merging #2414 (2c222a0) into main (4b6497c) will increase coverage by 0.00%.
The diff coverage is 91.54%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

@@           Coverage Diff           @@
##             main    #2414   +/-   ##
=======================================
  Coverage   93.68%   93.68%           
=======================================
  Files         176      177    +1     
  Lines       31061    31131   +70     
  Branches     2424     2430    +6     
=======================================
+ Hits        29099    29165   +66     
- Misses       1962     1966    +4     
Impacted Files Coverage Δ
src/javascript/eslint.ts 97.34% <88.23%> (-0.34%) ⬇️
src/js-config.ts 92.59% <92.59%> (ø)
src/javascript/node-package.ts 92.73% <0.00%> (+0.13%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Comment on lines +1 to +54
import { IResolver } from "./file";
import { JsonFileOptions } from "./json";
import { ObjectFile } from "./object-file";
import { Project } from "./project";

/**
* Options for the JsConfigFile class.
*/
export interface JsConfigFileOptions extends JsonFileOptions {}

/**
* Represents a JS configuratin file (e.g. .eslintrc.js).
* Suppor
*/
export class JsConfigFile extends ObjectFile {
static identifier(str: string): string {
return `${JsConfigFile.jsConfigFileIdentifier}${str}`;
}
private static readonly jsConfigFileIdentifier =
"__projenJsConfigFileIdentifier#";
constructor(
project: Project,
filePath: string,
options: JsConfigFileOptions
) {
super(project, filePath, options);
if (!options.obj) {
throw new Error('"obj" cannot be undefined');
}
}

protected synthesizeContent(resolver: IResolver): string | undefined {
const json = super.synthesizeContent(resolver);
if (!json) {
return undefined;
}

const sanitized = JSON.parse(json);
let content = JSON.stringify(sanitized, undefined, 2);
content = this.replaceIdentifier(content);
content = `module.exports = ${content};`;
content = `// ${this.marker}\n${content}`;

return content;
}

private replaceIdentifier(str: string): string {
const regex = new RegExp(
`(\"${JsConfigFile.jsConfigFileIdentifier})([a-zA-Z_$][a-zA-Z_$0-9]+)(\")`,
"gm"
);
return str.replace(regex, "$2");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should probably live inside the javascript submodule.

import { Projenrc } from "../../src/projenrc-json";
import { synthSnapshot } from "../util";

test(".eslintrc.js with identifier values", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Would also need some tests dedicated to JsConfigFile directly.

*/
export class JsConfigFile extends ObjectFile {
static identifier(str: string): string {
return `${JsConfigFile.jsConfigFileIdentifier}${str}`;
Copy link
Contributor

@mrgrain mrgrain Feb 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity what else did you try?

Using a function or an object with a toJSON() comes to mind, but not sure if it's still running through JSON.stringify adding quotes afterwards. Scrap that. Won't work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could build our own recursive renderer that prints out Json.stringify unless the value is a certain instanceof (could use Symbol or something custom).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a gist that does that: https://gist.github.com/mrgrain/a61408a4e8dd2006be5000a6523bf28e

But I wonder if that's enough or if people would want to start using imports as well. 🤔

* Write eslint configuration as JsConfig file instead of JSON
* @default false
*/
readonly jsConfig?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess at this point we should start replacing the boolean flags with a fileformat enum.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@tinovyatkin
Copy link
Contributor

tinovyatkin commented Feb 6, 2023

I hope we consider all complex environments before merging this proposal.

Legacy ESlint JS-config is not supporting ESM, so, if type: module is set in package.json then this will fail - it should be .cjs. If we want to have Javascript based config for ESLint then it makes sense to implement new Flat config support - that only can be eslint.config.(mc)js - that will be future proof, more performant and makes way more sense.

If the whole proposal is just a workaround for setting tsconfigRootDir then there are documented ways how to solve this problem - use array of tsconfigs in monorepo or globs or ESlint plugin settings - depending what you are trying to solve with tsconfigRootDir.

return undefined;
}

const sanitized = JSON.parse(json);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: (as I hope this will not be merged) but replaceIdentifier may be more robustly implemented as JSON reviver.

@mrgrain mrgrain marked this pull request as draft February 6, 2023 12:33
@mrgrain
Copy link
Contributor

mrgrain commented Feb 6, 2023

Drafting this PR as I clearly needs more discussions.

If the whole proposal is just a workaround for setting tsconfigRootDir then there are documented ways how to solve this problem - use array of tsconfigs in monorepo or globs or ESlint plugin settings - depending what you are trying to solve with tsconfigRootDir.

No I don't think it is. That might have been the issue that brought it up, but not being able to produce JS based configurations is certainly a gap in projen.

Legacy ESlint JS-config is not supporting ESM, so, if type: module is set in package.json then this will fail - it should be .cjs. If we want to have Javascript based config for ESLint then it makes sense to implement new Flat config support - that only can be eslint.config.(mc)js - that will be future proof, more performant and makes way more sense.

This is very interesting. I wonder how that fits within our current Eslint component in general.


To me this now looks like we have two problems at hand:

  • A generic JsConfigFile component that supports inlining code fragments into a generic configuration object. Needs to support both ESM and CJS.
  • How to integrate the above with the existing Eslint component

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

eslint: option to generate a js file for config
5 participants