ota-meshi / eslint-plugin-jsonc

ESLint plugin for JSON(C|5)? files
https://ota-meshi.github.io/eslint-plugin-jsonc/
MIT License
181 stars 17 forks source link

Flat configs have poor ergonomics #319

Closed alecmev closed 3 months ago

alecmev commented 3 months ago

Currently I have this:

import type { FlatConfig } from '@typescript-eslint/utils/ts-eslint';
import plugin from 'eslint-plugin-jsonc';
import parser from 'jsonc-eslint-parser';

const JSON_GLOB = '**/*.json';
const JSONC_GLOB = '**/{*.json{c,5},tsconfig*.json}';
const FILES = [JSON_GLOB, JSONC_GLOB];

export const JSONC_CONFIG: FlatConfig.Config[] = [
  {
    files: FILES,
    plugins: { jsonc: plugin },
    languageOptions: { parser },
  },
  {
    files: [JSON_GLOB],
    ignores: ['**/tsconfig*.json'],
    rules: plugin.configs['recommended-with-json'].rules as FlatConfig.Rules,
  },
  {
    files: [JSONC_GLOB],
    rules: plugin.configs['recommended-with-jsonc'].rules as FlatConfig.Rules,
  },
  {
    files: FILES,
    rules: {
      ...(plugin.configs.prettier.rules as FlatConfig.Rules),
      'jsonc/no-octal-escape': 'error',
    },
  },
]

The above is possible with flat/* too, but then I'll have to include flat/base explicitly, and do something like plugin.configs['flat/foo'].slice(1).map(x => ({ ...x, files: FILES })) for the rest of the configs.

Maybe there should be non-array exports available too? So something like this can be done:

[
  {
    ...plugin.configs['flat/base'],
    files: FILES,
  },
  {
    ...plugin.configs['flat/recommended-with-json'],
    files: [JSON_GLOB],
    ignores: ['**/tsconfig*.json'],
  },
  {
    ...plugin.configs['flat/recommended-with-jsonc'],
    files: [JSONC_GLOB],
  },
  {
    files: FILES,
    rules: {
      ...plugin.configs['flat/prettier'].rules,
      'jsonc/no-octal-escape': 'error',
    },
  },
]
ota-meshi commented 3 months ago

Thank you for posting the issue. Hmm. However, if we don't make those shareable configs into arrays, there is another problem. It makes it difficult for users to easily apply the same config to all JSON files. Perhaps it would be better to add a function to generate arbitrary configs? How do other plugin configurations solve this problem? 🤔

At this point I think it's easiest to modify files yourself like this:


export const JSONC_CONFIG: FlatConfig.Config[] = [
  ...plugin.configs['flat/base']
    .map(conf => ({
      ...conf,
      files: FILES
    })),
  ...plugin.configs['recommended-with-json']
    .map(conf => ({
      ...conf,
      files: [JSON_GLOB],
      ignores: ['**/tsconfig*.json'],
    })),
  // ...
]
alecmev commented 3 months ago

I think the main problem is that flat/base is an array. If it were an object then this could be very simple:

const BASE = {
  plugins: {
    get jsonc(): ESLint.Plugin {
      // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
      return require("../../index");
    },
  },
  files: [
    // ...
  ],
  languageOptions: { parser },
  rules: {
    // ...
  },
};

const FOO = {
  ...BASE,
  rules: {
    // ...
  }
}

const BAR = {
  ...BASE,
  rules: {
    // ...
  }
}

Repeating plugins and languageOptions isn't an issue (and they are currently repeated anyway).

Also, other plugins don't tend to have default files. E.g., neither eslint-plugin-jest nor eslint-plugin-react do.

ota-meshi commented 3 months ago

I use arrays intentionally and don't intend to change it, if you want a configuration using objects you can create your own shareable configuration.

alecmev commented 3 months ago

I use arrays intentionally

That's a valid approach, of course, but what's your rationale? What's the advantage over objects?

ota-meshi commented 3 months ago

The rules provided by this plugin are designed to be usable in .vue, so the plugin doesn't want to depend on the extension. But the parser depends on the extension, so it needs to be configured with an array.

alecmev commented 3 months ago

I see. What's the current recommended way to configure this plugin with Vue?

Would you consider encouraging people to do it explicitly maybe, instead of having a globally-available plugin?

const BASE = {
  plugins: { jsonc },
};

const BASE_JSON = {
  ...BASE,
  files: [ /* ... */ ],
  languageOptions: { parser },
  rules: { /* ... */ },
};

const JSON = {
  ...BASE_JSON,
  rules: { /* ... */ },
};

const VUE = {
  ...BASE,
  files: ["**/*.vue/*.json"], // I'm basing this on how eslint-plugin-markdown works
  rules: { /* ... */ },
}
ota-meshi commented 3 months ago

I'm intentionally using arrays as explained, if you really don't want to use arrays you can just publish your own shareable configuration.

alecmev commented 3 months ago

I understand the problem you're solving by using arrays, I have no horse in this race, was just trying to offer a more ergonomic approach. I can publish my own configs, of course, but nobody will use that, realistically.

Thanks for making this!