Compare commits
114 Commits
@eslit/cor
...
renovate/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2ee60f848 | ||
|
|
504deabcb6 | ||
|
|
fa9667ef09 | ||
|
|
263e1edb63 | ||
|
|
3aed0f1708 | ||
|
|
4a1f38ff24 | ||
|
|
2e1914c733 | ||
|
|
dcce924286 | ||
|
|
8749e51c13 | ||
|
|
853b868a24 | ||
|
|
f4e52b991c | ||
|
|
15765c8e73 | ||
|
|
b41d515e7f | ||
|
|
3ccab1d5b2 | ||
|
|
47eb42405b | ||
|
|
d8fcf7bfe3 | ||
|
|
71442056ce | ||
|
|
da3429a868 | ||
|
|
2ca40e1ef1 | ||
|
|
3434620ff1 | ||
|
|
f874ed622e | ||
|
|
ac7c70e81c | ||
|
|
ef6b399bb7 | ||
|
|
8ec97a8b66 | ||
|
|
a2cc881053 | ||
|
|
10c4155283 | ||
|
|
da8d4312a0 | ||
|
|
5dfa998860 | ||
|
|
8c86b748ad | ||
|
|
fc2613f874 | ||
|
|
a64062f3a5 | ||
|
|
b6ebbf9eb8 | ||
|
|
dd1d0276b0 | ||
|
|
5a6102ff45 | ||
|
|
8f77f4591f | ||
|
|
e20c7e6425 | ||
|
|
88412b067d | ||
|
|
d5e30078d4 | ||
|
|
b28c7d2c62 | ||
|
|
9043156913 | ||
|
|
3f773f5636 | ||
|
|
aad3b68f65 | ||
|
|
c3b34735dd | ||
|
|
3c23f0b07c | ||
|
|
ff44d8b56e | ||
|
|
c1aa5d0235 | ||
|
|
0be127d50a | ||
|
|
c061fdc8cd | ||
|
|
26f29009d6 | ||
|
|
da21030000 | ||
|
|
39d323a3ce | ||
|
|
3e866ee562 | ||
|
|
3bedc6d1ba | ||
|
|
64a45cd86a | ||
|
|
10e543094f | ||
|
|
b257ed000f | ||
|
|
5537a71bf3 | ||
|
|
e28cacfbd6 | ||
|
|
7d663d7ddf | ||
|
|
6fda82d3bc | ||
|
|
64dc504e2a | ||
|
|
a830ec71bd | ||
|
|
13e517964c | ||
|
|
4792b485d6 | ||
|
|
568bdb5d97 | ||
|
|
9a9ffc1a04 | ||
|
|
5eb7eac8ab | ||
|
|
983d4958f2 | ||
|
|
978f06605e | ||
|
|
1b2891b7ee | ||
|
|
4384f6143a | ||
|
|
107be3d4ab | ||
|
|
95ad4abf9f | ||
|
|
d2adda8aeb | ||
|
|
e775d83ccf | ||
|
|
c201a25e6e | ||
|
|
b0e00d6e5c | ||
|
|
73b71033b3 | ||
|
|
f24aba4f8e | ||
|
|
8c1e721346 | ||
|
|
c1f4c262dc | ||
|
|
f7b6faff09 | ||
|
|
48b70de8d9 | ||
|
|
fdad363313 | ||
|
|
1296891431 | ||
|
|
03a9ce3de5 | ||
|
|
5752e76197 | ||
|
|
86c178419c | ||
|
|
41fd41bef6 | ||
|
|
6f1fca2513 | ||
|
|
48f20e94f7 | ||
|
|
e1747ee696 | ||
|
|
5fbc250edd | ||
|
|
2f75775f43 | ||
|
|
9bc8b4fc5b | ||
|
|
c647e6020b | ||
|
|
53e44d5126 | ||
|
|
a6dc972a5c | ||
|
|
5aaaa9ac0e | ||
|
|
ac9bfbd232 | ||
|
|
4533557f4f | ||
|
|
a1e23fa757 | ||
|
|
89f7fc83eb | ||
|
|
b6dc308639 | ||
|
|
e80455e2d4 | ||
|
|
428fdffaf4 | ||
|
|
a474f7277d | ||
|
|
f6c47d14f6 | ||
|
|
3fda9b9156 | ||
|
|
2246103ae6 | ||
|
|
2178a36f9b | ||
|
|
971bbcaa80 | ||
|
|
edb86e3928 | ||
|
|
855b101292 |
12
.changeset/chilly-tigers-promise.md
Normal file
12
.changeset/chilly-tigers-promise.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
"@eslegant/js": minor
|
||||
---
|
||||
|
||||
Added rules for NodeJS environments, using the eslint-plugin-n and eslint-plugin-security.
|
||||
|
||||
The added configs in the `recommended` object helps preventing issues
|
||||
such as using deprecated or unsupported APIs and warns about security issues.
|
||||
Building on top of the recommended configs of the plugins.
|
||||
|
||||
In the `strict` object they helps making the code more node-explicit, such as importing
|
||||
global variables (e.g. `process` needs to be imported from `node:process`).
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "loreddev/eslit" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
|
||||
5
.changeset/honest-flies-laugh.md
Normal file
5
.changeset/honest-flies-laugh.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@eslegant/js": minor
|
||||
---
|
||||
|
||||
Added new ESLint rules inspired by StandardJS.
|
||||
12
.changeset/itchy-comics-prove.md
Normal file
12
.changeset/itchy-comics-prove.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
"@eslegant/js": minor
|
||||
---
|
||||
|
||||
New rules structure.
|
||||
|
||||
Now all configs have at least `recommended` and `strict` variants, each having `error`, `warn` and `disabled`/`off` rule levels.
|
||||
They are exported under the `configs` object, and are separated by purpose.
|
||||
Presets are now exported under the `presets` object, being a easier way of enabling multiple configs at once.
|
||||
|
||||
The package has a more defined purpose, and will be used just for rules/configs related to
|
||||
JavaScript and TypeScript.
|
||||
6
.changeset/light-mails-hang.md
Normal file
6
.changeset/light-mails-hang.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@eslegant/cli": minor
|
||||
---
|
||||
|
||||
Now the cli exports a API that runs the application and the configs object needs to be passed to the Cli class, this way any other package can run and have their configs array.
|
||||
With this, the new command line interface that handles the actual configs of this repo is the "eslegant" package.
|
||||
6
.changeset/metal-bottles-bow.md
Normal file
6
.changeset/metal-bottles-bow.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@eslegant/js": patch
|
||||
"@eslegant/cli": patch
|
||||
---
|
||||
|
||||
Updated dependencies
|
||||
5
.changeset/pretty-buckets-cover.md
Normal file
5
.changeset/pretty-buckets-cover.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"eslegant": patch
|
||||
---
|
||||
|
||||
Created the ESLegant package, being now the actual command that runs the CLI with the ESLegant's configs
|
||||
5
.changeset/shy-steaks-report.md
Normal file
5
.changeset/shy-steaks-report.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@eslegant/js": minor
|
||||
---
|
||||
|
||||
Configs now export a `default` variation, where rule leves aren't overriden.
|
||||
5
.changeset/slimy-camels-play.md
Normal file
5
.changeset/slimy-camels-play.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@eslegant/cli": patch
|
||||
---
|
||||
|
||||
Fixed some small errors that could be thrown when prompts are canceled. Also fixed --merge-to-root cli argument not working and added list of packages that are installed on confirmation prompt.
|
||||
5
.changeset/swift-dragons-tell.md
Normal file
5
.changeset/swift-dragons-tell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-eslegant": patch
|
||||
---
|
||||
|
||||
Created the "create-eslegant" package, as a _alias_ to the eslegant package, so it is compatible with `npm init` or `npm create` commands
|
||||
6
.changeset/thirty-parrots-fry.md
Normal file
6
.changeset/thirty-parrots-fry.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@eslegant/js": minor
|
||||
---
|
||||
|
||||
New rules related to possible security vulnerabilities in JavaScript.
|
||||
Provided by `eslint-plugin-security` and `eslint-plugin-no-secrets`
|
||||
8
.changeset/tricky-avocados-draw.md
Normal file
8
.changeset/tricky-avocados-draw.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"create-eslegant": minor
|
||||
"eslegant": minor
|
||||
"@eslegant/js": minor
|
||||
"@eslegant/cli": minor
|
||||
---
|
||||
|
||||
Renamed all packages from "eslit" to "eslegant"
|
||||
5
.changeset/witty-dogs-speak.md
Normal file
5
.changeset/witty-dogs-speak.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@eslegant/js": patch
|
||||
---
|
||||
|
||||
Renamed @eslegant/config to @eslegant/js
|
||||
@@ -2,7 +2,7 @@ root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
6
.github/actions/pnpm-setup/action.yml
vendored
6
.github/actions/pnpm-setup/action.yml
vendored
@@ -13,6 +13,10 @@ inputs:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
scope:
|
||||
required: false
|
||||
type: string
|
||||
default: '@eslegant'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -21,6 +25,8 @@ runs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
scope: ${{ inputs.scope }}
|
||||
|
||||
- name: Install PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
4
.github/workflows/checks.yml
vendored
4
.github/workflows/checks.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
4
.github/workflows/mirror.yml
vendored
4
.github/workflows/mirror.yml
vendored
@@ -8,8 +8,8 @@ on:
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
if: github.repository == 'loreddev/eslit'
|
||||
if: github.repository == 'loreddev/eslegant'
|
||||
uses: loreddev/.github/.github/workflows/mirrors.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
codeberg-repo: https://codeberg.org/LoredDev/ESLit
|
||||
codeberg-repo: https://codeberg.org/LoredDev/ESLegant
|
||||
|
||||
6
.github/workflows/release-branch.yml
vendored
6
.github/workflows/release-branch.yml
vendored
@@ -10,7 +10,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
update-release:
|
||||
if: ${{ github.repository == 'loreddev/eslit' }}
|
||||
if: ${{ github.repository == 'loreddev/eslegant' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
HUSKY: 0
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update dev branch
|
||||
if: steps.changesets.outputs.hasChangesets == 'false'
|
||||
if: ${{ steps.changesets.outputs.hasChangesets == 'false' && github.ref_name == 'main'}}
|
||||
uses: devmasx/merge-branch@master
|
||||
with:
|
||||
type: now
|
||||
|
||||
8
.github/workflows/release-preview.yml
vendored
8
.github/workflows/release-preview.yml
vendored
@@ -11,14 +11,14 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'loreddev/eslit' }}
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'loreddev/eslegant' }}
|
||||
name: Release preview
|
||||
env:
|
||||
HUSKY: 0
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -27,8 +27,10 @@ jobs:
|
||||
|
||||
- name: Update versions
|
||||
run: pnpm changeset version --snapshot next
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Release packages
|
||||
run: pnpm run release --tag next
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
97
.github/workflows/renovate-changesets.yml
vendored
Normal file
97
.github/workflows/renovate-changesets.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
# Credits to https://github.com/backstage/backstage/blob/master/.github/workflows/sync_renovate-changesets.yml
|
||||
|
||||
name: "📦 Renovate Changesets"
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- '.github/workflows/renovate-changesets.yml'
|
||||
- '**/pnpm-lock.yaml'
|
||||
|
||||
jobs:
|
||||
generate-changeset:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'renovate[bot]' && github.repository == 'loreddev/eslegant'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.email action@github.io
|
||||
git config --global user.name 'Github changeset workflow'
|
||||
- name: Generate changeset
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const { promises: fs } = require("fs");
|
||||
// Parses package.json files and returns the package names
|
||||
async function getPackagesNames(files) {
|
||||
const names = [];
|
||||
for (const file of files) {
|
||||
const data = JSON.parse(await fs.readFile(file, "utf8"));
|
||||
if (!data.private) {
|
||||
names.push(data.name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
async function createChangeset(fileName, packageBumps, packages) {
|
||||
let message = "";
|
||||
for (const [pkg, bump] of packageBumps) {
|
||||
message = message + `Updated dependency \`${pkg}\` to \`${bump}\`.\n`;
|
||||
}
|
||||
|
||||
const pkgs = packages.map((pkg) => `'${pkg}': patch`).join("\n");
|
||||
const body = `---\n${pkgs}\n---\n\n${message.trim()}\n`;
|
||||
await fs.writeFile(fileName, body);
|
||||
}
|
||||
|
||||
async function getBumps(files) {
|
||||
const bumps = new Map();
|
||||
for (const file of files) {
|
||||
const { stdout: changes } = await exec.getExecOutput("git", [
|
||||
"show",
|
||||
file,
|
||||
]);
|
||||
for (const change of changes.split("\n")) {
|
||||
if (!change.startsWith("+ ")) {
|
||||
continue;
|
||||
}
|
||||
const match = change.match(/"(.*?)"/g);
|
||||
bumps.set(match[0].replace(/"/g, ""), match[1].replace(/"/g, ""));
|
||||
}
|
||||
}
|
||||
return bumps;
|
||||
}
|
||||
|
||||
const branch = await exec.getExecOutput("git branch --show-current");
|
||||
if (!branch.stdout.startsWith("renovate/")) {
|
||||
console.log("Not a renovate branch, skipping");
|
||||
return;
|
||||
}
|
||||
const diffOutput = await exec.getExecOutput("git diff --name-only HEAD~1");
|
||||
const diffFiles = diffOutput.stdout.split("\n");
|
||||
if (diffFiles.find((f) => f.startsWith(".changeset"))) {
|
||||
console.log("Changeset already exists, skipping");
|
||||
return;
|
||||
}
|
||||
const files = diffFiles
|
||||
.filter((file) => file !== "package.json") // skip root package.json
|
||||
.filter((file) => file.includes("package.json"));
|
||||
const packageNames = await getPackagesNames(files);
|
||||
if (!packageNames.length) {
|
||||
console.log("No package.json changes to published packages, skipping");
|
||||
return;
|
||||
}
|
||||
const { stdout: shortHash } = await exec.getExecOutput(
|
||||
"git rev-parse --short HEAD"
|
||||
);
|
||||
const fileName = `.changeset/renovate-${shortHash.trim()}.md`;
|
||||
|
||||
const packageBumps = await getBumps(files);
|
||||
await createChangeset(fileName, packageBumps, packageNames);
|
||||
await exec.exec("git", ["add", fileName]);
|
||||
await exec.exec("git commit -C HEAD --amend --no-edit");
|
||||
await exec.exec("git push --force");
|
||||
@@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
ESLINT_USE_FLAT_CONFIG=true
|
||||
|
||||
pnpm dlx commitlint --edit ${1}
|
||||
pnpm dlx lint-staged
|
||||
|
||||
53
.vscode/project.code-workspace
vendored
Normal file
53
.vscode/project.code-workspace
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "configs/js",
|
||||
"path": "../configs/js"
|
||||
},
|
||||
{
|
||||
"name": "packages/cli",
|
||||
"path": "../packages/cli"
|
||||
},
|
||||
{
|
||||
"name": "packages/create-eslegant",
|
||||
"path": "../packages/create-eslegant"
|
||||
},
|
||||
{
|
||||
"name": "packages/eslegant",
|
||||
"path": "../packages/eslegant"
|
||||
},
|
||||
{
|
||||
"name": "ROOT",
|
||||
"path": "../"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"eslint.workingDirectories": ["./"],
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.organizeImports": false,
|
||||
},
|
||||
// The following is optional.
|
||||
// It's better to put under project setting `.vscode/settings.json`
|
||||
// to avoid conflicts with working with different eslint configs
|
||||
// that does not support all formats.
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
],
|
||||
"cSpell.words": [
|
||||
"eslegant"
|
||||
]
|
||||
}
|
||||
}
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -21,5 +21,13 @@
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
],
|
||||
"cSpell.words": [
|
||||
"eslegant",
|
||||
"estree",
|
||||
"nanospinner",
|
||||
"picocolors",
|
||||
"picomatch",
|
||||
"sisteransi"
|
||||
]
|
||||
}
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 [fullname]
|
||||
Copyright (c) 2023-present Gustavo "Guz" L. de Mello <contact.guz013@gmail.com> (https://guz.one)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
8
configs/js/CHANGELOG.md
Normal file
8
configs/js/CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# @eslit/config
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Rewritten most of the package logic, so now it uses a more standard ESLint configuration object structure. All configurations now are separated in scope and presets are created for better convenience when configuring ESLint. ([#8](https://github.com/LoredDev/ESLit/pull/8))
|
||||
(fixes [#3](https://github.com/loreddev/eslit/issues/3)).
|
||||
48
configs/js/docs/notes/duplicated-rules.md
Normal file
48
configs/js/docs/notes/duplicated-rules.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Duplicated rules
|
||||
|
||||
This is a list of rules that implements the same features and/or end
|
||||
up fixing the same errors.
|
||||
|
||||
- **[`@typescript/member-ordering`][ts/member-ordering], [`@typescript/sort-type-constituents`][ts/sort-type-constituents], [`import/order`][im/order]**:
|
||||
implements the same functions from [`eslint-plugin-perfectionist`][plugin-perfectionist]
|
||||
|
||||
- **[`unicorn/no-for-loop`][un/no-for-loop] and [`@typescript/prefer-for-of`][ts/prefer-for-of]**:
|
||||
`unicorn/no-for-loop` was used as it also reports when `i`/`index` is used.
|
||||
|
||||
- **[`unicorn/prefer-includes`][un/prefer-includes] and [`@typescript/prefer-includes`][ts/prefer-includes]**:
|
||||
`unicorn/prefer-includes` was used as it not needs type information to run.
|
||||
|
||||
- **[`@typescript/prefer-regexp-exec`][ts/prefer-regexp-exec] and [`unicorn/prefer-regexp-test`][un/prefer-regexp-test]**:
|
||||
`unicorn/prefer-regexp-exec` was used, because it reports on `RegExp#exec()` and `String#match()`
|
||||
|
||||
- **[`import/extensions`][im/extensions] and [`n/file-extension-in-import`][n/file-extension-in-import]**:
|
||||
`import/extensions` was used, as it is not a node-specific issue.
|
||||
|
||||
- **[`import/no-extraneous-dependencies`][im/no-extraneous-dependencies], [`n/no-extraneous-require`][n/no-extraneous-require] and [`n/no-extraneous-import`][n/no-extraneous-import]**:
|
||||
`import/no-extraneous-dependencies` was used, as it is not a node-specific issue.
|
||||
|
||||
- **[`import/no-unresolved`][im/no-unresolved], [`n/no-missing-require`][n/no-missing-require] and [`n/no-missing-import`][n/no-missing-import]**:
|
||||
`import/no-unresolved` was used, as it is not a node-specific issue.
|
||||
|
||||
[ts/member-ordering]: <https://typescript-eslint.io/rules/member-ordering>
|
||||
[ts/prefer-for-of]: <https://typescript-eslint.io/rules/prefer-for-of>
|
||||
[ts/prefer-includes]: <https://typescript-eslint.io/rules/prefer-includes>
|
||||
[ts/prefer-regexp-exec]: <https://typescript-eslint.io/rules/prefer-regexp-exec>
|
||||
[ts/sort-type-constituents]: <https://typescript-eslint.io/rules/sort-type-constituents>
|
||||
|
||||
[un/no-for-loop]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md>
|
||||
[un/prefer-includes]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-includes.md>
|
||||
[un/prefer-regexp-test]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md>
|
||||
|
||||
[im/order]: <https://github.com/import-js/eslint-plugin-import/blob/maib/docs/rules/order.md>
|
||||
[im/extensions]: <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/extensions.md>
|
||||
[im/no-extraneous-dependencies]: <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md>
|
||||
[im/no-unresolved]: <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md>
|
||||
|
||||
[n/file-extension-in-import]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/file-extension-in-import.md>
|
||||
[n/no-extraneous-import]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-extraneous-import.md>
|
||||
[n/no-extraneous-require]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-extraneous-require.md>
|
||||
[n/no-missing-import]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-import.md>
|
||||
[n/no-missing-require]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-require.md>
|
||||
|
||||
[plugin-perfectionist]: <https://eslint-plugin-perfectionist.azat.io/>
|
||||
5
configs/js/jsconfig.json
Normal file
5
configs/js/jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": ["./node_modules/**", "./dist/**"],
|
||||
"include": ["src/index.d.ts", "./src/**/*.ts", "./src/**/*.js", "src/index.js"],
|
||||
}
|
||||
64
configs/js/package.json
Normal file
64
configs/js/package.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "@eslegant/js",
|
||||
"version": "0.2.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"module": "./src/index.js",
|
||||
"source": "./src/index.js",
|
||||
"files": [
|
||||
"src",
|
||||
"index.d.ts"
|
||||
],
|
||||
"homepage": "https://github.com/LoredDev/ESLegant",
|
||||
"exports": {
|
||||
"default": "./src/index.js",
|
||||
"import": "./src/index.js",
|
||||
"types": "./src/index.d.ts"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "./src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"repository": {
|
||||
"directory": "configs/js",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LoredDev/ESLegant"
|
||||
},
|
||||
"author": {
|
||||
"email": "contact.guz013@gmail.com",
|
||||
"name": "Gustavo \"Guz\" L. de Mello",
|
||||
"url": "https://guz.one"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/eslint__js": "^8.42.0",
|
||||
"@types/node": "^20.5.3",
|
||||
"eslint": "^8.47.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^2.1.2",
|
||||
"@eslint/js": "^8.47.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
"@typescript-eslint/parser": "^6.4.1",
|
||||
"eslint-import-resolver-typescript": "^3.6.0",
|
||||
"eslint-plugin-compat": "^4.2.0",
|
||||
"eslint-plugin-i": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "^46.5.0",
|
||||
"eslint-plugin-n": "^16.0.2",
|
||||
"eslint-plugin-no-secrets": "^0.8.9",
|
||||
"eslint-plugin-perfectionist": "^1.5.1",
|
||||
"eslint-plugin-security": "^1.7.1",
|
||||
"eslint-plugin-unicorn": "^48.0.1",
|
||||
"globals": "^13.21.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.45.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
24
configs/js/src/@types/eslint-plugin-compat.d.ts
vendored
Normal file
24
configs/js/src/@types/eslint-plugin-compat.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `eslint-plugin-compat` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
/**
|
||||
* @summary Check the browser compatibility of your code.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with
|
||||
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||
* for plugins not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/eslint-plugin-compat npm package}
|
||||
*/
|
||||
declare module 'eslint-plugin-compat' {
|
||||
declare const plugin: ESLint.Plugin;
|
||||
export default plugin;
|
||||
}
|
||||
|
||||
35
configs/js/src/@types/eslint-plugin-i.d.ts
vendored
Normal file
35
configs/js/src/@types/eslint-plugin-i.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `eslint-plugin-i` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line unicorn/prevent-abbreviations
|
||||
import type { ESLint, Linter } from 'eslint';
|
||||
|
||||
/**
|
||||
* @summary ESLint plugin with rules that help validate proper imports.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with
|
||||
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||
* for plugins not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/eslint-plugin-import npm package}
|
||||
*/
|
||||
declare module 'eslint-plugin-i' {
|
||||
interface importEslintPlugin extends ESLint.Plugin {
|
||||
configs: {
|
||||
recommended: {
|
||||
rules: Linter.RulesRecord,
|
||||
},
|
||||
typescript: {
|
||||
rules: Linter.RulesRecord,
|
||||
},
|
||||
},
|
||||
}
|
||||
declare const plugin: importEslintPlugin;
|
||||
export default plugin;
|
||||
}
|
||||
|
||||
34
configs/js/src/@types/eslint-plugin-jsdoc.d.ts
vendored
Normal file
34
configs/js/src/@types/eslint-plugin-jsdoc.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `eslint-plugin-jsdoc` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
/**
|
||||
* @summary JSDoc specific linting rules for ESLint.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with
|
||||
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||
* for plugins not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.org/package/eslint-plugin-jsdoc npm package}
|
||||
*/
|
||||
declare module 'eslint-plugin-jsdoc' {
|
||||
// eslint-disable-next-line unicorn/prevent-abbreviations
|
||||
interface jsDocESlintPlugin extends ESLint.Plugin {
|
||||
configs: ESLint.Plugin['configs'] & {
|
||||
recommended: ESLint.ConfigData,
|
||||
'recommended-error': ESLint.ConfigData,
|
||||
'recommended-typescript': ESLint.ConfigData,
|
||||
'recommended-typescript-error': ESLint.ConfigData,
|
||||
'recommended-typescript-flavor': ESLint.ConfigData,
|
||||
'recommended-typescript-flavor-error': ESLint.ConfigData,
|
||||
},
|
||||
}
|
||||
declare const plugin: jsDocESlintPlugin;
|
||||
export default plugin;
|
||||
}
|
||||
24
configs/js/src/@types/eslint-plugin-n.d.ts
vendored
Normal file
24
configs/js/src/@types/eslint-plugin-n.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `eslint-plugin-n` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
/**
|
||||
* @summary Additional ESLint's rules for Node.js.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with
|
||||
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||
* for plugins not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/eslint-plugin-n npm package}
|
||||
*/
|
||||
declare module 'eslint-plugin-n' {
|
||||
declare const plugin: ESLint.Plugin;
|
||||
export default plugin;
|
||||
}
|
||||
|
||||
24
configs/js/src/@types/eslint-plugin-no-secrets.d.ts
vendored
Normal file
24
configs/js/src/@types/eslint-plugin-no-secrets.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `eslint-plugin-no-secrets` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
/**
|
||||
* @summary An eslint plugin to find strings that might be secrets/credentials.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with
|
||||
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||
* for plugins not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/eslint-plugin-no-secrets npm package}
|
||||
*/
|
||||
declare module 'eslint-plugin-no-secrets' {
|
||||
declare const plugin: ESLint.Plugin;
|
||||
export default plugin;
|
||||
}
|
||||
|
||||
24
configs/js/src/@types/eslint-plugin-security.d.ts
vendored
Normal file
24
configs/js/src/@types/eslint-plugin-security.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `eslint-plugin-security` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
/**
|
||||
* @summary ESLint rules for Node Security.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with
|
||||
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||
* for plugins not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/eslint-plugin-security npm package}
|
||||
*/
|
||||
declare module 'eslint-plugin-security' {
|
||||
declare const plugin: ESLint.Plugin;
|
||||
export default plugin;
|
||||
}
|
||||
|
||||
18
configs/js/src/@types/globals.d.ts
vendored
Normal file
18
configs/js/src/@types/globals.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `globals` package in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
declare module 'globals' {
|
||||
const globals: {
|
||||
browser: { [rule: string]: boolean, },
|
||||
builtin: { [rule: string]: boolean, },
|
||||
commonjs: { [rule: string]: boolean, },
|
||||
node: { [rule: string]: boolean, },
|
||||
nodeBuiltin: { [rule: string]: boolean, },
|
||||
};
|
||||
export default globals;
|
||||
}
|
||||
@@ -1,46 +1,53 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declaration for the `@typescript-eslint/eslint-plugin` and
|
||||
* `@typescript-eslint/parser` packages in a attempt to make it
|
||||
* compatible with the new flat config.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { ESLint, Linter } from 'eslint';
|
||||
|
||||
/**
|
||||
* @see {@link https://www.npmjs.com/package/@typescript-eslint/eslint-plugin npm package}
|
||||
*
|
||||
* @summary An ESLint plugin which provides lint rules for TypeScript codebases.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with ESLint new flat
|
||||
* config types. ESlint already has backwards compatibility for plugins not created in the
|
||||
* new flat config.
|
||||
* **Note:** Types in this project where overridden to be compatible with ESLint
|
||||
* new flat config types. ESlint already has backwards compatibility for plugins
|
||||
* not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/@typescript-eslint/eslint-plugin npm package}
|
||||
*/
|
||||
declare module '@typescript-eslint/eslint-plugin' {
|
||||
interface typescriptEslintPlugin extends ESLint.Plugin {
|
||||
configs: {
|
||||
recommended: {
|
||||
rules: Linter.RulesRecord
|
||||
}
|
||||
'recommended-requiring-type-checking': {
|
||||
rules: Linter.RulesRecord
|
||||
}
|
||||
'eslint-recommended': {
|
||||
rules: Linter.RulesRecord
|
||||
}
|
||||
rules: Linter.RulesRecord,
|
||||
},
|
||||
recommended: {
|
||||
rules: Linter.RulesRecord,
|
||||
},
|
||||
'recommended-requiring-type-checking': {
|
||||
rules: Linter.RulesRecord,
|
||||
},
|
||||
strict: {
|
||||
rules: Linter.RulesRecord
|
||||
}
|
||||
}
|
||||
rules: Linter.RulesRecord,
|
||||
},
|
||||
},
|
||||
}
|
||||
declare const plugin: typescriptEslintPlugin;
|
||||
export default plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link https://www.npmjs.com/package/@typescript-eslint/parser npm package}
|
||||
*
|
||||
* @summary An ESLint parser which leverages TypeScript ESTree to allow for ESLint
|
||||
* to lint TypeScript source code.
|
||||
* @summary An ESLint parser which leverages TypeScript ESTree to
|
||||
* allow for ESLint to lint TypeScript source code.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with ESLint new flat
|
||||
* config types. ESlint already has backwards compatibility for parsers not created in the
|
||||
* new flat config.
|
||||
* **Note:** Types in this project where overridden to be compatible
|
||||
* with ESLint new flat config types. ESlint already has backwards
|
||||
* compatibility for parsers not created in the new flat config.
|
||||
* @see {@link https://www.npmjs.com/package/@typescript-eslint/parser npm package}
|
||||
*/
|
||||
declare module '@typescript-eslint/parser' {
|
||||
declare const parser: Linter.ParserModule;
|
||||
64
configs/js/src/configs/core.js
Normal file
64
configs/js/src/configs/core.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file
|
||||
* Configuration object that adds necessary plugins for the other objects.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import process from 'node:process';
|
||||
|
||||
import tsESLint from '@typescript-eslint/eslint-plugin';
|
||||
import securityPlugin from 'eslint-plugin-security';
|
||||
import unicornPlugin from 'eslint-plugin-unicorn';
|
||||
// @ts-expect-error because the package doesn't export correct types
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import jsdocPlugin from 'eslint-plugin-jsdoc';
|
||||
import importPlugin from 'eslint-plugin-i';
|
||||
import globals from 'globals';
|
||||
|
||||
// eslint-disable-next-line import/no-relative-parent-imports
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
|
||||
/** @type {import('eslint').Linter.FlatConfig} */
|
||||
const config = {
|
||||
files: FILES,
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.builtin,
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: process.env.ESLEGANT_TSCONFIG ?? [
|
||||
'./{ts,js}config{.eslint,}.json',
|
||||
'./*/{ts,js}config{.eslint,}.json',
|
||||
'./*/*/{ts,js}config{.eslint,}.json',
|
||||
],
|
||||
tsconfigRootDir: process.env.ESLEGANT_ROOT ?? process.cwd(),
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tsESLint,
|
||||
'import': importPlugin,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
'jsdoc': jsdocPlugin,
|
||||
// @ts-expect-error because eslint-plugin-security doesn't export correct types
|
||||
'security': securityPlugin,
|
||||
// @ts-expect-error because eslint-plugin-unicorn doesn't export correct types
|
||||
'unicorn': unicornPlugin,
|
||||
},
|
||||
settings: {
|
||||
'import/extensions': FILES,
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': FILES,
|
||||
},
|
||||
'import/resolver': {
|
||||
node: true,
|
||||
typescript: true,
|
||||
},
|
||||
'jsdoc/mode': 'typescript',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
56
configs/js/src/configs/documentation.js
Normal file
56
configs/js/src/configs/documentation.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects that helps document your code.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
rules: {
|
||||
...{}, // Plugin: eslint-plugin-jsdoc
|
||||
'jsdoc/match-description': 'error',
|
||||
'jsdoc/require-description-complete-sentence': 'error',
|
||||
'jsdoc/require-hyphen-before-param-description': ['error', 'always'],
|
||||
'jsdoc/require-param-description': 'error',
|
||||
'jsdoc/require-property-description': 'error',
|
||||
'jsdoc/require-returns-check': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
|
||||
...{}, // Plugin: eslint-plugin-jsdoc
|
||||
'jsdoc/require-description': 'error',
|
||||
'jsdoc/require-file-overview': ['error', { tags: {
|
||||
author: {
|
||||
mustExist: true,
|
||||
},
|
||||
copyright: {
|
||||
initialCommentsOnly: true,
|
||||
},
|
||||
file: {
|
||||
initialCommentsOnly: true,
|
||||
mustExist: true,
|
||||
preventDuplicates: true,
|
||||
},
|
||||
license: {
|
||||
initialCommentsOnly: true,
|
||||
mustExist: true,
|
||||
preventDuplicates: true,
|
||||
},
|
||||
} }],
|
||||
},
|
||||
});
|
||||
|
||||
const documentation = { recommended, strict };
|
||||
export default documentation;
|
||||
51
configs/js/src/configs/environments/browser.js
Normal file
51
configs/js/src/configs/environments/browser.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects for the browser environment.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import compatPlugin from 'eslint-plugin-compat';
|
||||
import globals from 'globals';
|
||||
|
||||
import { createVariations } from '../../lib/rule-variations.js';
|
||||
import { FILES } from '../../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
compat: compatPlugin,
|
||||
},
|
||||
rules: {
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'unicorn/prefer-add-event-listener': 'error',
|
||||
'unicorn/prefer-dom-node-append': 'error',
|
||||
'unicorn/prefer-dom-node-dataset': 'error',
|
||||
'unicorn/prefer-dom-node-remove': 'error',
|
||||
'unicorn/prefer-dom-node-text-content': 'error',
|
||||
'unicorn/prefer-keyboard-event-key': 'error',
|
||||
'unicorn/prefer-modern-dom-apis': 'error',
|
||||
'unicorn/prefer-query-selector': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-compat
|
||||
'compat/compat': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
},
|
||||
});
|
||||
|
||||
const browser = { recommended, strict };
|
||||
export default browser;
|
||||
12
configs/js/src/configs/environments/index.js
Normal file
12
configs/js/src/configs/environments/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @file
|
||||
* Configuration object for the all the environment.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import browser from './browser.js';
|
||||
import node from './node.js';
|
||||
|
||||
const environments = { browser, node };
|
||||
export default environments;
|
||||
116
configs/js/src/configs/environments/node.js
Normal file
116
configs/js/src/configs/environments/node.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects for the NodeJS environment.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import nodePlugin from 'eslint-plugin-n';
|
||||
import globals from 'globals';
|
||||
|
||||
import { createVariations } from '../../lib/rule-variations.js';
|
||||
import { FILES } from '../../constants.js';
|
||||
|
||||
const commonjs = createVariations({
|
||||
files: ['**/*.cts', '**/*.cjs'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.nodeBuiltin,
|
||||
...globals.commonjs,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
// @ts-expect-error because types are not defined in 'eslint-plugin-n'
|
||||
n: nodePlugin,
|
||||
},
|
||||
rules: {
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'unicorn/prefer-module': 'off',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/no-commonjs': 'off',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-n
|
||||
'n/global-require': 'error',
|
||||
'n/no-exports-assign': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-security
|
||||
'security/detect-non-literal-require': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.nodeBuiltin,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
// @ts-expect-error because types are not defined in 'eslint-plugin-n'
|
||||
n: nodePlugin,
|
||||
},
|
||||
rules: {
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/no-dynamic-require': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-n
|
||||
'n/no-deprecated-api': 'error',
|
||||
'n/no-process-exit': 'error',
|
||||
'n/no-unpublished-bin': 'error',
|
||||
'n/no-unpublished-import': 'error',
|
||||
'n/no-unpublished-require': 'error',
|
||||
'n/no-unsupported-features/es-builtins': 'error',
|
||||
'n/no-unsupported-features/es-syntax': 'error',
|
||||
'n/no-unsupported-features/node-builtins': 'error',
|
||||
'n/process-exit-as-throw': 'error',
|
||||
'n/shebang': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-security
|
||||
'security/detect-buffer-noassert': 'warn',
|
||||
'security/detect-child-process': 'warn',
|
||||
'security/detect-new-buffer': 'warn',
|
||||
'security/detect-no-csrf-before-method-override': 'warn',
|
||||
'security/detect-non-literal-fs-filename': 'warn',
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
|
||||
...{}, // Plugin: eslint-plugin-n
|
||||
'n/no-new-require': 'error',
|
||||
'n/no-path-concat': 'error',
|
||||
'n/prefer-global/buffer': ['error', 'never'],
|
||||
'n/prefer-global/console': ['error', 'always'],
|
||||
'n/prefer-global/process': ['error', 'never'],
|
||||
'n/prefer-global/text-decoder': ['error', 'always'],
|
||||
'n/prefer-global/text-encoder': ['error', 'always'],
|
||||
'n/prefer-global/url': ['error', 'always'],
|
||||
'n/prefer-global/url-search-params': ['error', 'always'],
|
||||
'n/prefer-promises/dns': 'error',
|
||||
'n/prefer-promises/fs': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-security
|
||||
'security/detect-buffer-noassert': 'error',
|
||||
'security/detect-child-process': 'error',
|
||||
'security/detect-new-buffer': 'error',
|
||||
'security/detect-no-csrf-before-method-override': 'error',
|
||||
'security/detect-non-literal-fs-filename': 'warn',
|
||||
},
|
||||
});
|
||||
|
||||
const node = { commonjs, recommended, strict };
|
||||
export default node;
|
||||
192
configs/js/src/configs/formatting.js
Normal file
192
configs/js/src/configs/formatting.js
Normal file
@@ -0,0 +1,192 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects for code formatting and style in JavaScript and TypeScript.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import perfectionistPlugin from 'eslint-plugin-perfectionist';
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
plugins: {
|
||||
// @ts-expect-error because plugin doesn't export correct type
|
||||
perfectionist: perfectionistPlugin,
|
||||
},
|
||||
rules: {
|
||||
...{}, // ESLint rules
|
||||
'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
|
||||
'comma-style': 'error',
|
||||
'curly': ['error', 'multi-or-nest', 'consistent'],
|
||||
'dot-location': ['error', 'property'],
|
||||
'eol-last': 'error',
|
||||
'generator-star-spacing': ['error', 'before'],
|
||||
'no-mixed-spaces-and-tabs': 'error',
|
||||
'no-multi-spaces': 'error',
|
||||
'no-whitespace-before-property': 'error',
|
||||
'padded-blocks': ['error', 'never'],
|
||||
'rest-spread-spacing': 'error',
|
||||
'semi-spacing': 'error',
|
||||
'space-in-parens': 'error',
|
||||
'space-unary-ops': 'error',
|
||||
'spaced-comment': ['error', 'always', {
|
||||
block: {
|
||||
balanced: true,
|
||||
exceptions: ['*'],
|
||||
markers: ['!'],
|
||||
},
|
||||
line: {
|
||||
exceptions: ['/', '#'],
|
||||
markers: ['/'],
|
||||
},
|
||||
}],
|
||||
'template-curly-spacing': ['error', 'never'],
|
||||
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
'@typescript-eslint/block-spacing': ['error', 'always'],
|
||||
'@typescript-eslint/brace-style': ['error', 'stroustrup', {
|
||||
allowSingleLine: true,
|
||||
}],
|
||||
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
||||
'@typescript-eslint/comma-spacing': 'error',
|
||||
'@typescript-eslint/dot-notation': 'error',
|
||||
'@typescript-eslint/func-call-spacing': 'error',
|
||||
'@typescript-eslint/indent': ['error', 'tab', { ArrayExpression: 1,
|
||||
CallExpression: { arguments: 1 },
|
||||
FunctionDeclaration: { body: 1,
|
||||
parameters: 1 },
|
||||
FunctionExpression: { body: 1,
|
||||
parameters: 1 },
|
||||
ImportDeclaration: 1,
|
||||
MemberExpression: 1,
|
||||
ObjectExpression: 1,
|
||||
SwitchCase: 1,
|
||||
VariableDeclarator: 1,
|
||||
flatTernaryExpressions: false,
|
||||
ignoreComments: false, ignoredNodes: [
|
||||
'TemplateLiteral *',
|
||||
'JSXElement',
|
||||
'JSXElement > *',
|
||||
'JSXAttribute',
|
||||
'JSXIdentifier',
|
||||
'JSXNamespacedName',
|
||||
'JSXMemberExpression',
|
||||
'JSXSpreadAttribute',
|
||||
'JSXExpressionContainer',
|
||||
'JSXOpeningElement',
|
||||
'Element',
|
||||
'JSXFragment',
|
||||
'JSXOpeningFragment',
|
||||
'JSXClosingFragment',
|
||||
'JSXText',
|
||||
'JSXEmptyExpression',
|
||||
'JSXSpreadChild',
|
||||
'TSTypeParameterInstantiation',
|
||||
'FunctionExpression > .params[decorators.length > 0]',
|
||||
// eslint-disable-next-line max-len
|
||||
'FunctionExpression > .params > :matches(Decorator, :not(:first-child))',
|
||||
// eslint-disable-next-line max-len
|
||||
'ClassBody.body > PropertyDefinition[decorators.length > 0] > .key',
|
||||
], offsetTernaryExpressions: true, outerIIFEBody: 1,
|
||||
}],
|
||||
'@typescript-eslint/key-spacing': ['error', {
|
||||
afterColon: true,
|
||||
beforeColon: false,
|
||||
}],
|
||||
'@typescript-eslint/keyword-spacing': ['error', {
|
||||
after: true,
|
||||
before: true,
|
||||
}],
|
||||
'@typescript-eslint/lines-between-class-members': ['error'],
|
||||
'@typescript-eslint/member-delimiter-style': ['error', {
|
||||
multiline: { delimiter: 'comma', requireLast: true },
|
||||
singleline: { delimiter: 'comma', requireLast: true },
|
||||
}],
|
||||
'@typescript-eslint/no-extra-parens': ['error', 'functions'],
|
||||
'@typescript-eslint/object-curly-spacing': ['error', 'always'],
|
||||
'@typescript-eslint/quotes': ['error', 'single'],
|
||||
'@typescript-eslint/semi': ['error', 'always'],
|
||||
'@typescript-eslint/space-before-blocks': ['error', 'always'],
|
||||
'@typescript-eslint/space-before-function-paren': ['error', {
|
||||
anonymous: 'always',
|
||||
asyncArrow: 'always',
|
||||
named: 'never',
|
||||
}],
|
||||
'@typescript-eslint/space-infix-ops': 'error',
|
||||
'@typescript-eslint/type-annotation-spacing': ['error', {
|
||||
after: true,
|
||||
before: false,
|
||||
overrides: {
|
||||
arrow: { after: true, before: true },
|
||||
},
|
||||
}],
|
||||
'block-spacing': 'off',
|
||||
'brace-style': 'off',
|
||||
'comma-dangle': 'off',
|
||||
'comma-spacing': 'off',
|
||||
'dot-notation': 'off',
|
||||
'func-call-spacing': 'off',
|
||||
'indent': 'off',
|
||||
'key-spacing': 'off',
|
||||
'keyword-spacing': 'off',
|
||||
'lines-between-class-members': 'off',
|
||||
'object-curly-spacing': 'off',
|
||||
'quotes': 'off',
|
||||
'semi': 'off',
|
||||
'space-before-blocks': 'off',
|
||||
'space-before-function-paren': 'off',
|
||||
'space-infix-ops': 'off',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||
'import/exports-last': 'error',
|
||||
'import/first': 'error',
|
||||
'import/group-exports': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-perfectionist
|
||||
'perfectionist/sort-array-includes': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-classes': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-enums': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-exports': ['error', { type: 'line-length' }],
|
||||
'perfectionist/sort-imports': ['error', {
|
||||
groups: [
|
||||
'type',
|
||||
'builtin',
|
||||
'external',
|
||||
'internal-type',
|
||||
'internal',
|
||||
['parent-type', 'sibling-type', 'index-type'],
|
||||
['parent', 'sibling', 'index'],
|
||||
'object',
|
||||
'unknown',
|
||||
],
|
||||
order: 'desc',
|
||||
type: 'line-length',
|
||||
}],
|
||||
'perfectionist/sort-interfaces': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-jsx-props': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-map-elements': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-named-exports': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-named-imports': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-object-types': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-objects': ['error', { type: 'natural' }],
|
||||
'perfectionist/sort-union-types': ['error', { type: 'natural' }],
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
},
|
||||
});
|
||||
|
||||
const formatting = { recommended, strict };
|
||||
export default formatting;
|
||||
322
configs/js/src/configs/index.d.ts
vendored
Normal file
322
configs/js/src/configs/index.d.ts
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* @file
|
||||
* Type declarations and documentations for all configs objects.
|
||||
* All these types are public to the user.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
interface ConfigVariations {
|
||||
/**
|
||||
* @summary
|
||||
* Enable rules with the predefined levels of the package.
|
||||
* @description
|
||||
* Most of the rules in ESLegant are on `error` level. This
|
||||
* was preferred so it is harder to ignore them. But it has
|
||||
* some small exceptions where rules will be at `warn` level,
|
||||
* being more as a "reminder" than a actual rule.
|
||||
*
|
||||
* If you want to **every** rule in the config to have an
|
||||
* `error` or `warn` level, you can use the other variants.
|
||||
*/
|
||||
default: Linter.FlatConfig,
|
||||
/**
|
||||
* @description
|
||||
* Enable all rules with `error` level.
|
||||
*/
|
||||
error: Linter.FlatConfig,
|
||||
/**
|
||||
* @description
|
||||
* Disable all rules in this config.
|
||||
*/
|
||||
off: Linter.FlatConfig,
|
||||
/**
|
||||
* @description
|
||||
* Enable all rules with `warn` level.
|
||||
*/
|
||||
warn: Linter.FlatConfig,
|
||||
}
|
||||
|
||||
const configs: Readonly<{
|
||||
/**
|
||||
* @summary
|
||||
* **Add this configuration at the start of your array**.
|
||||
* @description
|
||||
* This config adds necessary plugins and configuration for ESLint
|
||||
* to use in the other configs **This should always be in the top
|
||||
* of the configuration array**.
|
||||
*/
|
||||
core: Linter.FlatConfig,
|
||||
/**
|
||||
* @description
|
||||
* This configuration helps you document your code with JSDoc.
|
||||
*/
|
||||
documentation: {
|
||||
/**
|
||||
* @summary
|
||||
* Recommended rules from the original plugins.
|
||||
* @description
|
||||
* Less strict rules, that nots enforces documentation on
|
||||
* every aspect/function of your code. Recommended rules for
|
||||
* projects in prototyping or starting phases, or apps that
|
||||
* doesn't needs massive documentations.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @summary
|
||||
* More opinionated configuration, created for ESLegant and Lored's projects.
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @description
|
||||
* Enforces some documentation of every function and file.
|
||||
* Better for libraries (in more production-ready phases),
|
||||
* large codebase's and/or projects with multiple teams/people.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
/**
|
||||
* @summary
|
||||
* Configurations related to specific JavaScript/TypeScript environments,
|
||||
* such as Node, Deno and the Browser.
|
||||
* @description
|
||||
* Adds global variables and rules related to code on said environments.
|
||||
* **They can and should be combined** depending on the codebase you're
|
||||
* working with.
|
||||
*/
|
||||
environments: {
|
||||
/**
|
||||
* @summary
|
||||
* Browser environment configuration, use this if you are working
|
||||
* on a pure client-side or mixed codebase environment.
|
||||
* @description
|
||||
* Warns about possible incompatible Web APIs on your codebase, you can
|
||||
* configure the target browsers using {@link https://github.com/browserslist/browserslist browserslist}
|
||||
* on `package.json`.
|
||||
*/
|
||||
browser: {
|
||||
/**
|
||||
* @description
|
||||
* Recommends end enforces the use of newer web APIs
|
||||
* on your codebase.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @todo
|
||||
* For now, the strict rules is a alias of the recommended,
|
||||
* as it not currently adds any new rules.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
node: {
|
||||
/**
|
||||
* @summary
|
||||
* Configuration overrides for CommonJS files.
|
||||
* @description
|
||||
* ESLegant recommends the use of ESModules and newer syntax
|
||||
* for javascript files. This configuration should mostly be
|
||||
* used for exceptions, like config files that don't support
|
||||
* ESM.
|
||||
*
|
||||
* This configuration just affects files ending in `*.cjs`
|
||||
* and `*.cts` extensions. If you want to use in other files
|
||||
* consider creating a config object.
|
||||
* @example <caption>Example of using the config for specific CommonJS files</caption>
|
||||
* import { configs } from '@eslegant/js';
|
||||
*
|
||||
* export default [
|
||||
* {
|
||||
* ...configs.environments.node.commonjs.error
|
||||
* files: ['*.config.js'],
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
commonjs: ConfigVariations,
|
||||
/**
|
||||
* @description
|
||||
* Recommends newer and best practices on NodeJS projects.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @todo
|
||||
* For now, the strict rules is a alias of the recommended,
|
||||
* as it not currently adds any new rules.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @summary
|
||||
* This config relates to code formatting and style in JavaScript and TypeScript.
|
||||
* @description
|
||||
* This configuration enforces a specific code formatting and style in JavaScript
|
||||
* and TypeScript. The purpose of it can sometimes overlap with the `suggestions`
|
||||
* config, so to separate better, this tries to mostly enforces just rules for
|
||||
* how the code looks and is organized than coding style/way of handling something.
|
||||
*/
|
||||
formatting: {
|
||||
/**
|
||||
* @summary
|
||||
* ESLegant / Lored's code style configuration.
|
||||
* @description
|
||||
* This configuration recommends a opinionated code and formatting style, which
|
||||
* is mostly similar to other styles in the JavaScript environment.
|
||||
* It is based on the work and config of Anthony's ESLint config (`antfu/eslint-config`),
|
||||
* with the most notable changes being the use of `tabs` instead of 2 space indentation
|
||||
* and the use of semicolons.
|
||||
* @see {@link https://github.com/antfu/eslint-config Anthony's config}
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @todo
|
||||
* For now, the strict rules is a alias of the recommended,
|
||||
* as it not currently adds any new rules.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
/**
|
||||
* @description
|
||||
* This configuration enforces a specific naming convention for the codebase.
|
||||
* With the object of making the code more readable and understandable.
|
||||
*/
|
||||
naming: {
|
||||
/**
|
||||
* @summary
|
||||
* Prevents bad naming conventions and behavior.
|
||||
* @description
|
||||
* This configuration prevents bad names and behaviors such as abbreviations.
|
||||
* It does not enforces specific names or naming structure/strategies.
|
||||
* **With the exception of** file names being enforces to be `kebab-case`.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @summary
|
||||
* Enforces specific naming structure/strategies.
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @description
|
||||
* This configuration enforces specific names or naming structure/strategies for your
|
||||
* code. Enforcing things such using verbs and nouns in specif orders and
|
||||
* when abbreviations are accepted or not.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
overrides: {
|
||||
'inferrable-types': ConfigVariations,
|
||||
performance: ConfigVariations,
|
||||
},
|
||||
/**
|
||||
* @summary
|
||||
* Prevents possible syntax errors in your code.
|
||||
* @description
|
||||
* This configuration object prevents possible syntax and code logic
|
||||
* errors on your file. Mostly not opinionated.
|
||||
*/
|
||||
problems: {
|
||||
/**
|
||||
* @description
|
||||
* Rules which prevents most errors in your code. Based
|
||||
* mostly on ESLint's recommended configuration.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @description
|
||||
* Extra-safety rules, reporting possible forgettable errors
|
||||
* or errors in typing.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
/**
|
||||
* @summary
|
||||
* Prevents possible vulnerabilities.
|
||||
* @description
|
||||
* This configuration tries to prevent possible vulnerabilities
|
||||
* in you code, such as hard-coded secrets, personal information in comments,
|
||||
* XSS attacks, etc.
|
||||
*/
|
||||
security: {
|
||||
/**
|
||||
* @description
|
||||
* Rules which warns you about possible security vulnerabilities.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @description
|
||||
* Similar to recommended config, but with rules in error-level
|
||||
* to make possible vulnerabilities harder to ignore.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
/**
|
||||
* @summary
|
||||
* Enforces different ways of coding in JavaScript and TypeScript.
|
||||
* @description
|
||||
* This configuration enforces different ways doing things, coding style and/or
|
||||
* code logic patterns. Preferring over explicit and declarative code than
|
||||
* implicit.
|
||||
*/
|
||||
suggestions: {
|
||||
/**
|
||||
* @summary
|
||||
* Recommended for projects in prototyping/starting phases.
|
||||
* @description
|
||||
* This configuration enforces mostly best practices,
|
||||
* based on the `recommended` options of the plugins.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @summary
|
||||
* Strict rules that takes "guarding rails" for your code.
|
||||
* @borrows Builds on top of the recommended configuration
|
||||
* @description
|
||||
* **This will get in the way of your programming**. This configuration
|
||||
* tries prevent possible bad code smells and practices that could built
|
||||
* up when your project grows. Enforcing you to refactor more the code
|
||||
* and separating and or reorganizing functions and code logic.
|
||||
* @see {@link https://youtu.be/CFRhGnuXG-4 Example: Why You Shouldn't Nest Your Code - by: CodeAesthetic}
|
||||
* - The maximum depth allowed is 4. (`max-depth: [error, 4]`)
|
||||
* @see {@link https://youtu.be/J1f5b4vcxCQ Example: Dependency Injection, The Best Pattern - by: CodeAesthetic}
|
||||
* - Files should be organized in a tree-like structure, and shouldn't import modules
|
||||
* in parent directories. This helps you organize your code and suggests using
|
||||
* dependency injection more.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
/**
|
||||
* @summary
|
||||
* Rules for TypeScript files specifically. **Use this if
|
||||
* you have Typescript files in your project**.
|
||||
* Affects `*.ts`, `*.tsx`, `*.mts` and `*.cts` files.
|
||||
* @description
|
||||
* Most of TypeScript rules can be applied to type-checked JavaScript
|
||||
* files also. But some can just be fixed in TypeScript syntax, so they
|
||||
* where disabled and moved to this specific configuration.
|
||||
*
|
||||
* It also disable things that aren't useful when using TypeScript, such
|
||||
* as types in JSDoc comments.
|
||||
*
|
||||
* **This should be placed after the `suggestion` config.**.
|
||||
*
|
||||
*/
|
||||
'suggestions-typescript': {
|
||||
/**
|
||||
* @summary
|
||||
* Rules similar to {@link configs.suggestions.recommended `suggestions#recommended`},
|
||||
* but for TypeScript.
|
||||
*/
|
||||
recommended: ConfigVariations,
|
||||
/**
|
||||
* @summary
|
||||
* Rules similar to {@link configs.suggestions.strict `suggestions#strict`},
|
||||
* but for TypeScript.
|
||||
*/
|
||||
strict: ConfigVariations,
|
||||
},
|
||||
}>;
|
||||
|
||||
export default configs;
|
||||
export type { ConfigVariations };
|
||||
32
configs/js/src/configs/index.js
Normal file
32
configs/js/src/configs/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable import/max-dependencies */
|
||||
/**
|
||||
* @file
|
||||
* Main export files for all the configs objects, merging then in one `configs` object.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import typescript from './suggestions-typescript.js';
|
||||
import environments from './environments/index.js';
|
||||
import documentation from './documentation.js';
|
||||
import suggestions from './suggestions.js';
|
||||
import formatting from './formatting.js';
|
||||
import overrides from './overrides.js';
|
||||
import security from './security.js';
|
||||
import problems from './problems.js';
|
||||
import naming from './naming.js';
|
||||
import core from './core.js';
|
||||
|
||||
const configs = {
|
||||
core,
|
||||
documentation,
|
||||
environments,
|
||||
formatting,
|
||||
naming,
|
||||
overrides,
|
||||
problems,
|
||||
security,
|
||||
suggestions,
|
||||
'suggestions-typescript': typescript,
|
||||
};
|
||||
export default configs;
|
||||
40
configs/js/src/configs/naming.js
Normal file
40
configs/js/src/configs/naming.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects which enforces a specific naming convention for the codebase.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
rules: {
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'unicorn/filename-case': ['error', { case: 'kebabCase' }],
|
||||
/*
|
||||
* TODO [>=1.0.0]: This will be replaced by a better naming convention.
|
||||
* 'unicorn/prevent-abbreviations': 'error',
|
||||
*/
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
// '@typescript-eslint/naming-convention': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'unicorn/no-keyword-prefix': 'error',
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
const suggestions = { recommended, strict };
|
||||
export default suggestions;
|
||||
35
configs/js/src/configs/overrides.js
Normal file
35
configs/js/src/configs/overrides.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Overrides for specific scenarios or preferences of users. The objects
|
||||
* are supposed to be placed on the end of the arrays and can
|
||||
* change configs of multiple other categories.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
* @todo This file is not completed fully.
|
||||
*/
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES, TS_FILES } from '../constants.js';
|
||||
|
||||
// TODO [>=1.0.0]: Create a separate config for performance related practices
|
||||
const performance = createVariations({
|
||||
files: FILES,
|
||||
rules: {
|
||||
'prefer-object-spread': 'off',
|
||||
'prefer-spread': 'off',
|
||||
},
|
||||
});
|
||||
|
||||
const inferrableTypes = createVariations({
|
||||
files: TS_FILES,
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-inferrable-types': 'error',
|
||||
'@typescript-eslint/typedef': 'off',
|
||||
},
|
||||
});
|
||||
|
||||
const overrides = { 'inferrable-types': inferrableTypes, performance };
|
||||
export default overrides;
|
||||
109
configs/js/src/configs/problems.js
Normal file
109
configs/js/src/configs/problems.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects for preventing possible syntax errors.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
rules: {
|
||||
...{}, // ESLint rules
|
||||
'constructor-super': 'error',
|
||||
'for-direction': 'error',
|
||||
'getter-return': 'error',
|
||||
'no-async-promise-executor': 'error',
|
||||
'no-class-assign': 'error',
|
||||
'no-compare-neg-zero': 'error',
|
||||
'no-cond-assign': 'error',
|
||||
'no-const-assign': 'error',
|
||||
'no-constant-condition': 'error',
|
||||
'no-control-regex': 'error',
|
||||
'no-debugger': 'error',
|
||||
'no-delete-var': 'error',
|
||||
'no-dupe-args': 'error',
|
||||
'no-dupe-class-members': 'error',
|
||||
'no-dupe-else-if': 'error',
|
||||
'no-dupe-keys': 'error',
|
||||
'no-duplicate-case': 'error',
|
||||
'no-empty-character-class': 'error',
|
||||
'no-empty-pattern': 'error',
|
||||
'no-ex-assign': 'error',
|
||||
'no-fallthrough': 'error',
|
||||
'no-func-assign': 'error',
|
||||
'no-global-assign': 'error',
|
||||
'no-import-assign': 'error',
|
||||
'no-inner-declarations': 'error',
|
||||
'no-invalid-regexp': 'error',
|
||||
'no-irregular-whitespace': 'error',
|
||||
'no-misleading-character-class': 'error',
|
||||
'no-nonoctal-decimal-escape': 'error',
|
||||
'no-obj-calls': 'error',
|
||||
'no-octal': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-prototype-builtins': 'error',
|
||||
'no-regex-spaces': 'error',
|
||||
'no-self-assign': 'error',
|
||||
'no-setter-return': 'error',
|
||||
'no-shadow-restricted-names': 'error',
|
||||
'no-sparse-arrays': 'error',
|
||||
'no-this-before-super': 'error',
|
||||
'no-undef': 'error',
|
||||
'no-unexpected-multiline': 'error',
|
||||
'no-unreachable': 'error',
|
||||
'no-unsafe-finally': 'error',
|
||||
'no-unsafe-negation': 'error',
|
||||
'no-unsafe-optional-chaining': 'error',
|
||||
'no-unused-labels': 'error',
|
||||
'no-useless-backreference': 'error',
|
||||
'use-isnan': 'error',
|
||||
'valid-typeof': 'error',
|
||||
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
'@typescript-eslint/no-loss-of-precision': 'error',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'no-loss-of-precision': 'off',
|
||||
'no-redeclare': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/default': 'error',
|
||||
'import/export': 'error',
|
||||
'import/named': 'error',
|
||||
'import/namespace': 'error',
|
||||
'import/no-unresolved': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
|
||||
...{}, // ESLint rules
|
||||
'no-constant-binary-expression': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-new-native-nonconstructor': 'error',
|
||||
'no-promise-executor-return': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-template-curly-in-string': 'error',
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unreachable-loop': 'error',
|
||||
'no-unused-private-class-members': 'error',
|
||||
'require-atomic-updates': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
},
|
||||
});
|
||||
|
||||
const problems = { recommended, strict };
|
||||
export default problems;
|
||||
61
configs/js/src/configs/security.js
Normal file
61
configs/js/src/configs/security.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects for preventing possible security vulnerabilities.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import noSecretsPluginRegexes from 'eslint-plugin-no-secrets/regexes.js';
|
||||
import noSecretsPlugin from 'eslint-plugin-no-secrets';
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
plugins: {
|
||||
'no-secrets': noSecretsPlugin,
|
||||
},
|
||||
rules: {
|
||||
...{}, // Plugin: eslint-plugin-security
|
||||
'security/detect-bidi-characters': 'warn',
|
||||
'security/detect-disable-mustache-escape': 'warn',
|
||||
'security/detect-eval-with-expression': 'warn',
|
||||
'security/detect-non-literal-regexp': 'warn',
|
||||
'security/detect-object-injection': 'warn',
|
||||
'security/detect-possible-timing-attacks': 'warn',
|
||||
'security/detect-pseudoRandomBytes': 'warn',
|
||||
'security/detect-unsafe-regex': 'warn',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-no-secrets
|
||||
'no-secrets/no-secrets': 'warn',
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
|
||||
...{}, // Plugin: eslint-plugin-security
|
||||
'security/detect-bidi-characters': 'error',
|
||||
'security/detect-disable-mustache-escape': 'error',
|
||||
'security/detect-eval-with-expression': 'error',
|
||||
'security/detect-non-literal-regexp': 'error',
|
||||
'security/detect-object-injection': 'warn',
|
||||
'security/detect-possible-timing-attacks': 'warn',
|
||||
'security/detect-pseudoRandomBytes': 'error',
|
||||
'security/detect-unsafe-regex': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-no-secrets
|
||||
'no-secrets/no-secrets': ['error', {
|
||||
additionalRegexes: noSecretsPluginRegexes,
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
const security = { recommended, strict };
|
||||
export default security;
|
||||
40
configs/js/src/configs/suggestions-typescript.js
Normal file
40
configs/js/src/configs/suggestions-typescript.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects that enforces different ways of coding in TypeScript specifically.
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { TS_FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: [TS_FILES].flat(),
|
||||
rules: {
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-jsdoc
|
||||
'jsdoc/check-tag-names': ['error', { typed: true }],
|
||||
'jsdoc/no-types': 'error',
|
||||
'jsdoc/require-param-type': 'off',
|
||||
'jsdoc/require-property-type': 'off',
|
||||
'jsdoc/require-returns-type': 'off',
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
'@typescript-eslint/explicit-member-accessibility': 'error',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||
},
|
||||
});
|
||||
const typescript = { recommended, strict };
|
||||
export default typescript;
|
||||
453
configs/js/src/configs/suggestions.js
Normal file
453
configs/js/src/configs/suggestions.js
Normal file
@@ -0,0 +1,453 @@
|
||||
/* eslint-disable import/no-relative-parent-imports */
|
||||
/* eslint-disable unicorn/no-useless-spread */
|
||||
/**
|
||||
* @file
|
||||
* Configuration objects that enforces different ways of coding in JavaScript and TypeScript..
|
||||
* See more info on the configs type declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import { createVariations } from '../lib/rule-variations.js';
|
||||
import { FILES } from '../constants.js';
|
||||
|
||||
const recommended = createVariations({
|
||||
files: FILES,
|
||||
rules: {
|
||||
'camelcase': 'error',
|
||||
'max-len': ['error', {
|
||||
code: 80,
|
||||
comments: 100,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreUrls: true,
|
||||
}],
|
||||
'no-case-declarations': 'error',
|
||||
'no-confusing-arrow': 'error',
|
||||
'no-console': 'error',
|
||||
'no-constant-condition': 'error',
|
||||
'no-delete-var': 'error',
|
||||
'no-empty': 'error',
|
||||
'no-lonely-if': 'error',
|
||||
'no-new-symbol': 'error',
|
||||
'no-redeclare': 'error',
|
||||
'no-useless-catch': 'error',
|
||||
'no-useless-escape': 'error',
|
||||
'no-var': 'error',
|
||||
'no-with': 'error',
|
||||
'object-shorthand': ['error', 'always', {
|
||||
avoidQuotes: true,
|
||||
ignoreConstructors: false,
|
||||
}],
|
||||
'prefer-exponentiation-operator': 'error',
|
||||
'prefer-rest-params': 'error',
|
||||
'prefer-spread': 'error',
|
||||
'prefer-template': 'error',
|
||||
'quote-props': ['error', 'consistent-as-needed'],
|
||||
'require-yield': 'error',
|
||||
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
/*
|
||||
* '@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||
* Incompatible with perfectionist/sort-interfaces and
|
||||
* perfectionist/sort-object-types.
|
||||
*/
|
||||
'@typescript-eslint/array-type': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/ban-tslint-comment': 'error',
|
||||
'@typescript-eslint/ban-types': 'error',
|
||||
'@typescript-eslint/class-literal-property-style': 'error',
|
||||
'@typescript-eslint/consistent-generic-constructors': 'error',
|
||||
// eslint-disable-next-line max-len
|
||||
'@typescript-eslint/consistent-indexed-object-style': ['error', 'index-signature'],
|
||||
'@typescript-eslint/consistent-type-assertions': 'error',
|
||||
'@typescript-eslint/consistent-type-definitions': 'error',
|
||||
'@typescript-eslint/no-base-to-string': 'error',
|
||||
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-confusing-void-expression': 'error',
|
||||
'@typescript-eslint/no-duplicate-enum-values': 'error',
|
||||
'@typescript-eslint/no-duplicate-type-constituents': 'error',
|
||||
'@typescript-eslint/no-dynamic-delete': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-extra-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-extraneous-class': 'error',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-for-in-array': 'error',
|
||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||
'@typescript-eslint/no-invalid-void-type': 'error',
|
||||
'@typescript-eslint/no-meaningless-void-operator': 'error',
|
||||
'@typescript-eslint/no-misused-new': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/no-mixed-enums': 'error',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-redundant-type-constituents': 'error',
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'@typescript-eslint/no-this-alias': 'error',
|
||||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-arguments': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
|
||||
'@typescript-eslint/no-unsafe-argument': 'error',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'error',
|
||||
'@typescript-eslint/no-unsafe-call': 'error',
|
||||
'@typescript-eslint/no-unsafe-declaration-merging': 'error',
|
||||
'@typescript-eslint/no-unsafe-enum-comparison': 'error',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-var-requires': 'error',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
|
||||
'@typescript-eslint/prefer-as-const': 'error',
|
||||
'@typescript-eslint/prefer-function-type': 'error',
|
||||
'@typescript-eslint/prefer-literal-enum-member': 'error',
|
||||
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'error',
|
||||
'@typescript-eslint/prefer-optional-chain': 'error',
|
||||
'@typescript-eslint/prefer-reduce-type-parameter': 'error',
|
||||
'@typescript-eslint/prefer-return-this-type': 'error',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'error',
|
||||
'@typescript-eslint/restrict-plus-operands': 'error',
|
||||
'@typescript-eslint/restrict-template-expressions': 'error',
|
||||
'@typescript-eslint/triple-slash-reference': 'error',
|
||||
'@typescript-eslint/unbound-method': 'error',
|
||||
'@typescript-eslint/unified-signatures': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'unicorn/better-regex': 'error',
|
||||
'unicorn/catch-error-name': 'error',
|
||||
'unicorn/consistent-destructuring': 'error',
|
||||
'unicorn/consistent-function-scoping': 'error',
|
||||
'unicorn/empty-brace-spaces': 'error',
|
||||
'unicorn/error-message': 'error',
|
||||
'unicorn/escape-case': 'error',
|
||||
'unicorn/expiring-todo-comments': ['error', {
|
||||
allowWarningComments: false,
|
||||
}],
|
||||
'unicorn/explicit-length-check': 'error',
|
||||
'unicorn/new-for-builtins': 'error',
|
||||
'unicorn/no-abusive-eslint-disable': 'error',
|
||||
'unicorn/no-array-callback-reference': 'error',
|
||||
'unicorn/no-array-for-each': 'error',
|
||||
'unicorn/no-array-method-this-argument': 'error',
|
||||
'unicorn/no-array-push-push': 'error',
|
||||
'unicorn/no-array-reduce': 'error',
|
||||
'unicorn/no-await-expression-member': 'error',
|
||||
'unicorn/no-console-spaces': 'error',
|
||||
'unicorn/no-document-cookie': 'error',
|
||||
'unicorn/no-empty-file': 'error',
|
||||
'unicorn/no-for-loop': 'error',
|
||||
'unicorn/no-hex-escape': 'error',
|
||||
'unicorn/no-instanceof-array': 'error',
|
||||
'unicorn/no-invalid-remove-event-listener': 'error',
|
||||
'unicorn/no-lonely-if': 'error',
|
||||
'unicorn/no-new-array': 'error',
|
||||
'unicorn/no-new-buffer': 'error',
|
||||
'unicorn/no-object-as-default-parameter': 'error',
|
||||
'unicorn/no-process-exit': 'error',
|
||||
'unicorn/no-static-only-class': 'error',
|
||||
'unicorn/no-thenable': 'error',
|
||||
'unicorn/no-this-assignment': 'error',
|
||||
'unicorn/no-typeof-undefined': 'error',
|
||||
'unicorn/no-unnecessary-await': 'error',
|
||||
'unicorn/no-unreadable-array-destructuring': 'error',
|
||||
'unicorn/no-unreadable-iife': 'error',
|
||||
'unicorn/no-useless-fallback-in-spread': 'error',
|
||||
'unicorn/no-useless-length-check': 'error',
|
||||
'unicorn/no-useless-promise-resolve-reject': 'error',
|
||||
'unicorn/no-useless-spread': 'error',
|
||||
'unicorn/no-useless-switch-case': 'error',
|
||||
/*
|
||||
* TODO (@guz013) [>=1.0.0]: 'unicorn/no-useless-undefined' rule
|
||||
* It could be better to use a invert to this rule instead, as
|
||||
* explicity code could be better then implicitly.
|
||||
*/
|
||||
'unicorn/no-zero-fractions': 'error',
|
||||
'unicorn/number-literal-case': 'error',
|
||||
'unicorn/numeric-separators-style': 'error',
|
||||
'unicorn/prefer-array-find': ['error', { checkFromLast: true }],
|
||||
'unicorn/prefer-array-flat': 'error',
|
||||
'unicorn/prefer-array-flat-map': 'error',
|
||||
'unicorn/prefer-array-index-of': 'error',
|
||||
'unicorn/prefer-array-some': 'error',
|
||||
'unicorn/prefer-at': 'error',
|
||||
'unicorn/prefer-blob-reading-methods': 'error',
|
||||
'unicorn/prefer-code-point': 'error',
|
||||
'unicorn/prefer-date-now': 'error',
|
||||
'unicorn/prefer-default-parameters': 'error',
|
||||
'unicorn/prefer-event-target': 'error',
|
||||
'unicorn/prefer-export-from': 'error',
|
||||
'unicorn/prefer-includes': 'error',
|
||||
'unicorn/prefer-logical-operator-over-ternary': 'error',
|
||||
'unicorn/prefer-math-trunc': 'error',
|
||||
'unicorn/prefer-modern-math-apis': 'error',
|
||||
'unicorn/prefer-module': 'error',
|
||||
'unicorn/prefer-native-coercion-functions': 'error',
|
||||
'unicorn/prefer-negative-index': 'error',
|
||||
'unicorn/prefer-number-properties': 'error',
|
||||
'unicorn/prefer-optional-catch-binding': 'error',
|
||||
'unicorn/prefer-prototype-methods': 'error',
|
||||
'unicorn/prefer-reflect-apply': 'error',
|
||||
'unicorn/prefer-regexp-test': 'error',
|
||||
'unicorn/prefer-set-has': 'error',
|
||||
// TODO [>=1.0.0]: Should this be on a "performance" preset?
|
||||
'unicorn/prefer-set-size': 'error',
|
||||
// TODO [>=1.0.0]: Should this be disabled on a "performance" preset?
|
||||
'unicorn/prefer-spread': 'error',
|
||||
'unicorn/prefer-string-replace-all': 'error',
|
||||
'unicorn/prefer-string-slice': 'error',
|
||||
'unicorn/prefer-string-starts-ends-with': 'error',
|
||||
'unicorn/prefer-string-trim-start-end': 'error',
|
||||
'unicorn/prefer-switch': ['error', {
|
||||
emptyDefaultCase: 'do-nothing-comment',
|
||||
}],
|
||||
'unicorn/prefer-ternary': 'error',
|
||||
'unicorn/prefer-top-level-await': 'error',
|
||||
'unicorn/prefer-type-error': 'error',
|
||||
'unicorn/relative-url-style': 'error',
|
||||
'unicorn/require-array-join-separator': 'error',
|
||||
'unicorn/require-number-to-fixed-digits-argument': 'error',
|
||||
'unicorn/switch-case-braces': 'error',
|
||||
'unicorn/text-encoding-identifier-case': 'error',
|
||||
'unicorn/throw-new-error': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/no-amd': 'error',
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-deprecated': 'error',
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-empty-named-blocks': 'error',
|
||||
'import/no-named-as-default': 'error',
|
||||
'import/no-named-as-default-member': 'error',
|
||||
'import/no-self-import': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-jsdoc
|
||||
'jsdoc/check-alignment': 'error',
|
||||
'jsdoc/check-param-names': 'error',
|
||||
'jsdoc/check-property-names': 'error',
|
||||
'jsdoc/check-syntax': 'error',
|
||||
'jsdoc/check-tag-names': 'error',
|
||||
'jsdoc/check-types': 'error',
|
||||
'jsdoc/check-values': 'error',
|
||||
'jsdoc/empty-tags': 'error',
|
||||
'jsdoc/implements-on-classes': 'error',
|
||||
'jsdoc/multiline-blocks': 'error',
|
||||
'jsdoc/no-multi-asterisks': ['error', { allowWhitespace: true }],
|
||||
'jsdoc/require-asterisk-prefix': ['error', 'always'],
|
||||
'jsdoc/require-jsdoc': 'error',
|
||||
'jsdoc/require-param': 'error',
|
||||
'jsdoc/require-param-name': 'error',
|
||||
'jsdoc/require-param-type': 'error',
|
||||
'jsdoc/require-property': 'error',
|
||||
'jsdoc/require-property-name': 'error',
|
||||
'jsdoc/require-property-type': 'error',
|
||||
'jsdoc/require-returns': 'error',
|
||||
'jsdoc/require-returns-check': 'error',
|
||||
'jsdoc/require-returns-type': 'error',
|
||||
'jsdoc/require-throws': 'error',
|
||||
'jsdoc/require-yields': 'error',
|
||||
'jsdoc/require-yields-check': 'error',
|
||||
'jsdoc/sort-tags': 'error',
|
||||
// 'jsdoc/valid-types': 'error', This is already handled by Typescript type checking mostly
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
const strict = createVariations({
|
||||
...recommended.error,
|
||||
rules: {
|
||||
...recommended.error.rules,
|
||||
|
||||
...{}, // ESLint rules
|
||||
'accessor-pairs': 'error',
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
'block-scoped-var': 'error',
|
||||
'capitalized-comments': 'error',
|
||||
'complexity': ['error', 10],
|
||||
'consistent-return': 'error',
|
||||
'consistent-this': 'error',
|
||||
'default-case': 'error',
|
||||
'default-case-last': 'error',
|
||||
'eqeqeq': 'error',
|
||||
'func-name-matching': 'error',
|
||||
'func-names': ['error', 'as-needed'],
|
||||
'func-style': ['error', 'declaration'],
|
||||
'grouped-accessor-pairs': ['error', 'setBeforeGet'],
|
||||
'handle-callback-err': 'error',
|
||||
'logical-assignment-operators': ['error', 'always', {
|
||||
enforceForIfStatements: true,
|
||||
}],
|
||||
'max-classes-per-file': ['error', 1],
|
||||
'max-depth': ['error', 4],
|
||||
'max-lines': ['error', 500],
|
||||
'max-lines-per-function': ['error', {
|
||||
max: 60,
|
||||
skipBlankLines: true,
|
||||
skipComments: true,
|
||||
}],
|
||||
'max-nested-callbacks': ['error', 10],
|
||||
'max-params': ['error', 4],
|
||||
'max-statements': ['error', 15],
|
||||
'multiline-comment-style': ['error', 'starred-block'],
|
||||
'new-cap': 'error',
|
||||
'new-parens': 'error',
|
||||
'no-alert': 'error',
|
||||
'no-await-in-loop': 'error',
|
||||
'no-bitwise': 'error',
|
||||
'no-caller': 'error',
|
||||
'no-continue': 'error',
|
||||
'no-div-regex': 'error',
|
||||
'no-else-return': 'error',
|
||||
'no-empty-static-block': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-extra-bind': 'error',
|
||||
'no-extra-boolean-cast': 'error',
|
||||
// TODO [>=1.0.0]: Fix no-extra-parens conflict with the unicorn/no-nested-ternary rule.
|
||||
'no-extra-parens': ['error', 'functions'],
|
||||
'no-floating-decimal': 'error',
|
||||
'no-implicit-coercion': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-iterator': 'error',
|
||||
'no-labels': 'error',
|
||||
'no-lone-blocks': 'error',
|
||||
'no-mixed-operators': 'error',
|
||||
'no-multi-assign': 'error',
|
||||
'no-multi-str': 'error',
|
||||
'no-multiple-empty-lines': 'error',
|
||||
'no-negated-condition': 'error',
|
||||
'no-new': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-new-object': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-path-concat': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-script-url': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-undef-init': 'error',
|
||||
'no-underscore-dangle': 'error',
|
||||
'no-unneeded-ternary': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-use-before-define': 'error',
|
||||
'no-useless-call': 'error',
|
||||
'no-useless-computed-key': 'error',
|
||||
'no-useless-rename': 'error',
|
||||
'no-useless-return': 'error',
|
||||
'one-var': ['error', 'never'],
|
||||
'operator-assignment': ['error', 'always'],
|
||||
'operator-linebreak': 'error',
|
||||
'prefer-arrow-callback': 'error',
|
||||
'prefer-const': 'error',
|
||||
'prefer-named-capture-group': 'error',
|
||||
'prefer-numeric-literals': 'error',
|
||||
'prefer-object-has-own': 'error',
|
||||
'prefer-object-spread': 'error',
|
||||
'prefer-promise-reject-errors': 'error',
|
||||
'prefer-regex-literals': 'error',
|
||||
'radix': ['error', 'always'],
|
||||
'require-unicode-regexp': 'error',
|
||||
'symbol-description': 'error',
|
||||
'wrap-iife': 'error',
|
||||
'yoda': ['error', 'never'],
|
||||
|
||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||
'@typescript-eslint/class-methods-use-this': 'error',
|
||||
'@typescript-eslint/consistent-type-exports': ['error', {
|
||||
fixMixedExportsWithInlineTypeSpecifier: false,
|
||||
}],
|
||||
'@typescript-eslint/consistent-type-imports': ['error', {
|
||||
disallowTypeAnnotations: true,
|
||||
fixStyle: 'separate-type-imports',
|
||||
prefer: 'type-imports',
|
||||
}],
|
||||
'@typescript-eslint/default-param-last': 'error',
|
||||
'@typescript-eslint/dot-notation': 'error',
|
||||
'@typescript-eslint/method-signature-style': ['error', 'method'],
|
||||
'@typescript-eslint/no-array-constructor': 'error',
|
||||
'@typescript-eslint/no-empty-function': 'error',
|
||||
'@typescript-eslint/no-extra-semi': 'error',
|
||||
'@typescript-eslint/no-invalid-this': 'error',
|
||||
'@typescript-eslint/no-loop-func': 'error',
|
||||
'@typescript-eslint/no-shadow': 'error',
|
||||
'@typescript-eslint/no-throw-literal': 'error',
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'error',
|
||||
'@typescript-eslint/no-use-before-define': 'error',
|
||||
'@typescript-eslint/no-useless-constructor': 'error',
|
||||
'@typescript-eslint/no-useless-empty-export': 'error',
|
||||
'@typescript-eslint/parameter-properties': 'error',
|
||||
'@typescript-eslint/prefer-enum-initializers': 'error',
|
||||
'@typescript-eslint/prefer-readonly': 'error',
|
||||
'@typescript-eslint/promise-function-async': 'error',
|
||||
'@typescript-eslint/require-array-sort-compare': 'error',
|
||||
'@typescript-eslint/require-await': 'error',
|
||||
'@typescript-eslint/switch-exhaustiveness-check': 'error',
|
||||
'@typescript-eslint/typedef': 'error',
|
||||
// '@typescript-eslint/no-magic-numbers': ['error'],
|
||||
'class-methods-use-this': 'off',
|
||||
'default-param-last': 'off',
|
||||
'no-array-constructor': 'off',
|
||||
'no-empty-function': 'off',
|
||||
'no-extra-semi': 'off',
|
||||
'no-invalid-this': 'off',
|
||||
'no-loop-func': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-throw-literal': 'off',
|
||||
'no-use-before-defined': 'off',
|
||||
'no-useless-constructor': 'off',
|
||||
'require-await': 'off',
|
||||
// 'no-magic-numbers': 'off',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-unicorn
|
||||
'no-nested-ternary': 'off',
|
||||
'unicorn/custom-error-definition': 'error',
|
||||
'unicorn/no-negated-condition': 'error',
|
||||
'unicorn/no-nested-ternary': 'error',
|
||||
/*
|
||||
* TODO (@guz013) [>=1.0.0]: 'unicorn/no-null' rule
|
||||
* It could be better to use a invert to this rule instead,
|
||||
* because the null type could be a better representation to
|
||||
* "no value" then undefined.
|
||||
*/
|
||||
'unicorn/no-unsafe-regex': 'error',
|
||||
'unicorn/no-unused-properties': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-import
|
||||
'import/extensions': ['error', 'always', { ignorePackages: true }],
|
||||
'import/max-dependencies': ['error', {
|
||||
ignoreTypeImports: true,
|
||||
max: 10,
|
||||
}],
|
||||
'import/no-absolute-path': 'error',
|
||||
'import/no-anonymous-default-export': 'error',
|
||||
'import/no-cycle': 'error',
|
||||
'import/no-import-module-exports': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-named-default': 'error',
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-relative-parent-imports': 'error',
|
||||
'import/no-unassigned-import': ['error', {
|
||||
allow: ['**/*.css', '**/*.scss', '**/*.less'],
|
||||
}],
|
||||
'import/prefer-default-export': 'error',
|
||||
'import/unambiguous': 'error',
|
||||
|
||||
...{}, // Plugin: eslint-plugin-jsdoc
|
||||
'jsdoc/check-access': 'error',
|
||||
'jsdoc/check-indentation': 'error',
|
||||
'jsdoc/informative-docs': 'error',
|
||||
'jsdoc/multiline-blocks': ['error', { noSingleLineBlocks: true }],
|
||||
'jsdoc/no-bad-blocks': 'error',
|
||||
'jsdoc/no-blank-block-descriptions': 'error',
|
||||
'jsdoc/no-blank-blocks': 'error',
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
const suggestions = { recommended, strict };
|
||||
export default suggestions;
|
||||
29
configs/js/src/constants.js
Normal file
29
configs/js/src/constants.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @file
|
||||
* Constant values used around the package.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
const JS_FILES = [
|
||||
'**/*.js',
|
||||
'**/*.mjs',
|
||||
'**/*.cjs',
|
||||
'**/*.jsx',
|
||||
];
|
||||
const TS_FILES = [
|
||||
'**/*.ts',
|
||||
'**/*.mts',
|
||||
'**/*.cts',
|
||||
'**/*.tsx',
|
||||
];
|
||||
const FILES = [
|
||||
JS_FILES,
|
||||
TS_FILES,
|
||||
].flat();
|
||||
|
||||
export {
|
||||
FILES,
|
||||
JS_FILES,
|
||||
TS_FILES,
|
||||
};
|
||||
22
configs/js/src/index.d.ts
vendored
Normal file
22
configs/js/src/index.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file
|
||||
* Types entrypoint of the package.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export { default as presets } from './presets/index.d.ts';
|
||||
export { default as configs } from './configs/index.d.ts';
|
||||
|
||||
/**
|
||||
* Helper function to provide type-checking when defining
|
||||
* ESLint's configuration.
|
||||
*
|
||||
* @param config - The configuration array to be returned.
|
||||
* @returns The configuration array passed on the first parameter.
|
||||
*/
|
||||
export function defineConfig(
|
||||
config: Linter.FlatConfig[],
|
||||
): Linter.FlatConfig[];
|
||||
27
configs/js/src/index.js
Normal file
27
configs/js/src/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file
|
||||
* Main file entrypoint of the package.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import presets from './presets/index.js';
|
||||
import configs from './configs/index.js';
|
||||
|
||||
/**
|
||||
* Helper function to provide type-checking when defining
|
||||
* ESLint's configuration.
|
||||
*
|
||||
* @param {import('eslint').Linter.FlatConfig[]} config
|
||||
* - The configuration array to be returned.
|
||||
* @returns {import('eslint').Linter.FlatConfig[]}
|
||||
*/
|
||||
function defineConfig(config) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const eslegant = { configs, presets };
|
||||
|
||||
export { defineConfig, eslegant as default };
|
||||
export { default as configs } from './configs/index.js';
|
||||
export { default as presets } from './presets/index.js';
|
||||
104
configs/js/src/lib/rule-variations.js
Normal file
104
configs/js/src/lib/rule-variations.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @file Utility functions used in the package to manipulate rules records.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('../configs/index').ConfigVariations} ConfigVariations
|
||||
* @typedef {import('eslint').Linter.RuleEntry} RuleEntry
|
||||
* @typedef {import('eslint').Linter.RuleLevel} RuleLevel
|
||||
* @typedef {import('eslint').Linter.FlatConfig} FlatConfig
|
||||
*/
|
||||
|
||||
/**
|
||||
* Changes the level of a rule entry. Checking if it
|
||||
* is a Array or a simple RuleLevel entry.
|
||||
*
|
||||
* Useful in conjunction with {@link iterateRules `iterateRules()`} function.
|
||||
*
|
||||
* @param {Readonly<RuleEntry>} ruleEntry
|
||||
* - The rule entry to be modified.
|
||||
* @param {RuleLevel} level
|
||||
* - The new level to be passed to the rule.
|
||||
* @returns {RuleEntry}
|
||||
*/
|
||||
function changeLevel(ruleEntry, level) {
|
||||
if (typeof level === 'number') {
|
||||
/** @type {RuleLevel[]} */
|
||||
const levels = ['error', 'off', 'warn'];
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
level = levels[level];
|
||||
}
|
||||
|
||||
if (Array.isArray(ruleEntry))
|
||||
return [level, ruleEntry[1]];
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through a rule entry record, using the handler
|
||||
* on each entry and returns the resulting object.
|
||||
*
|
||||
* Useful for applying plugin prefixes or changing the rule
|
||||
* level of the entries.
|
||||
*
|
||||
* @param {Readonly<{[name: string]: RuleEntry}>} rules
|
||||
* - The object to be iterated through.
|
||||
* @param {([name, entry]: [string, RuleEntry]) => [string, RuleEntry]} handler
|
||||
* - Function to run on every rule entry.
|
||||
* @returns {{[name: string]: RuleEntry}} - The resulting object.
|
||||
*/
|
||||
function iterateRules(rules, handler) {
|
||||
const entries = Object.entries(rules);
|
||||
entries.map(entry => handler(entry));
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link ConfigVariations variations} for the given configuration object.
|
||||
* With `error`, `warn` and `off` rule levels.
|
||||
*
|
||||
* Used in the configuration objects of this package.
|
||||
*
|
||||
* @param {Readonly<FlatConfig>} config
|
||||
* - The configuration object to create `error`, `warn`, and `off` variations.
|
||||
* @returns {ConfigVariations}
|
||||
*/
|
||||
function createVariations(config) {
|
||||
const configError = {
|
||||
...config,
|
||||
rules: iterateRules(config.rules ?? {}, ([key, entry]) =>
|
||||
(entry === 'off' || (Array.isArray(entry) && entry[0] === 'off')
|
||||
? [key, entry]
|
||||
: [key, changeLevel(entry, 'error')]),
|
||||
),
|
||||
};
|
||||
|
||||
const configWarning = {
|
||||
...config,
|
||||
rules: iterateRules(config.rules ?? {}, ([key, entry]) =>
|
||||
(entry === 'off' || (Array.isArray(entry) && entry[0] === 'off')
|
||||
? [key, entry]
|
||||
: [key, changeLevel(entry, 'warn')]),
|
||||
),
|
||||
};
|
||||
|
||||
const configDisabled = {
|
||||
...config,
|
||||
rules: iterateRules(
|
||||
config.rules ?? {},
|
||||
([key, entry]) => [key, changeLevel(entry, 'off')],
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
default: config,
|
||||
error: configError,
|
||||
off: configDisabled,
|
||||
warn: configWarning,
|
||||
};
|
||||
}
|
||||
|
||||
export { createVariations, iterateRules };
|
||||
51
configs/js/src/presets/index.d.ts
vendored
Normal file
51
configs/js/src/presets/index.d.ts
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file
|
||||
* Types declarations and documentation for the presets object.
|
||||
* All these types are public to the user.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
const presets: Readonly<{
|
||||
/**
|
||||
* @summary
|
||||
* Preset recommended for projects in a prototyping or starting phase.
|
||||
* @description
|
||||
* This preset is mostly recommended for projects in the start of
|
||||
* their development, being more flexible and less opinionated.
|
||||
* Useful for preventing errors.
|
||||
*
|
||||
* Configs used:
|
||||
* - `problems#recommended`;
|
||||
* - `suggestions#recommended`;
|
||||
* - `suggestions-typescript#recommended`;
|
||||
* - `formatting#recommended`;
|
||||
* - `naming#recommended`;
|
||||
* - `documentation#recommended`;
|
||||
*
|
||||
* All configs are set on level `error`.
|
||||
*/
|
||||
recommended: Linter.FlatConfig[],
|
||||
/**
|
||||
* @summary
|
||||
* Preset recommended for projects in a production or large scale.
|
||||
* @description
|
||||
* This preset is more strict and opinionated, focusing on making
|
||||
* your code follow a specific structure and pattern.
|
||||
*
|
||||
* Configs used:
|
||||
* - `problems#strict`;
|
||||
* - `suggestions#strict`;
|
||||
* - `suggestions-typescript#strict`;
|
||||
* - `formatting#strict`;
|
||||
* - `naming#strict`;
|
||||
* - `documentation#recommended`;
|
||||
*
|
||||
* All configs are set on level `error`.
|
||||
*/
|
||||
strict: Linter.FlatConfig[],
|
||||
}>;
|
||||
|
||||
export default presets;
|
||||
12
configs/js/src/presets/index.js
Normal file
12
configs/js/src/presets/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @file
|
||||
* Presets object export.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
import recommended from './recommended.js';
|
||||
import strict from './strict.js';
|
||||
|
||||
const presets = { recommended, strict };
|
||||
export default presets;
|
||||
23
configs/js/src/presets/recommended.js
Normal file
23
configs/js/src/presets/recommended.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file
|
||||
* Recommended preset object. More info and docs on the type
|
||||
* declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/no-relative-parent-imports
|
||||
import configs from '../configs/index.js';
|
||||
|
||||
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||
const recommended = [
|
||||
configs.core,
|
||||
configs.problems.recommended.default,
|
||||
configs.suggestions.recommended.default,
|
||||
configs['suggestions-typescript'].recommended.default,
|
||||
configs.formatting.recommended.default,
|
||||
configs.naming.recommended.default,
|
||||
configs.documentation.recommended.default,
|
||||
configs.security.recommended.default,
|
||||
];
|
||||
export default recommended;
|
||||
23
configs/js/src/presets/strict.js
Normal file
23
configs/js/src/presets/strict.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file
|
||||
* Strict preset object. More info and docs on the type
|
||||
* declaration file.
|
||||
* @license MIT
|
||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/no-relative-parent-imports
|
||||
import configs from '../configs/index.js';
|
||||
|
||||
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||
const strict = [
|
||||
configs.core,
|
||||
configs.problems.strict.default,
|
||||
configs.suggestions.strict.default,
|
||||
configs['suggestions-typescript'].strict.default,
|
||||
configs.formatting.strict.default,
|
||||
configs.naming.strict.default,
|
||||
configs.documentation.recommended.default,
|
||||
configs.security.strict.default,
|
||||
];
|
||||
export default strict;
|
||||
@@ -1,7 +1,16 @@
|
||||
import { defineConfig } from '@eslit/core';
|
||||
import { configs, defineConfig, presets } from '@eslegant/js';
|
||||
|
||||
export default defineConfig({
|
||||
environment: {
|
||||
node: true,
|
||||
export default defineConfig([
|
||||
...presets.strict,
|
||||
configs.environments.node.strict.default,
|
||||
{
|
||||
...configs.documentation.strict.error,
|
||||
files: ['configs/**/*.js', 'configs/**/*.ts'],
|
||||
},
|
||||
});
|
||||
{
|
||||
files: ['**/*.{js,ts,cjs,tjs,mjs,mts,jsx,tsx}'],
|
||||
rules: {
|
||||
'jsdoc/check-values': ['error', { allowedLicenses: ['MIT'] }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
8
fixtures/library/CHANGELOG.md
Normal file
8
fixtures/library/CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# @eslit-fixtures/library
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1296891`](https://github.com/LoredDev/ESLit/commit/1296891431117c9a386d36d84c8d402013c3a094)]:
|
||||
- @eslit/cli@0.1.0
|
||||
16
fixtures/library/package.json
Normal file
16
fixtures/library/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "fixtures/library",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "pnpm cli"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslegant/cli": "workspace:*"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
36
fixtures/monorepo/.gitignore
vendored
Normal file
36
fixtures/monorepo/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
15
fixtures/monorepo/package.json
Normal file
15
fixtures/monorepo/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:cli": "pnpm cli"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslegant/cli": "workspace:*"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.10",
|
||||
"name": "fixtures/monorepo",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@eslit-fixtures/svelte",
|
||||
"name": "fixtures/svelte",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -7,9 +7,11 @@
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "ESLINT_USE_FLAT_CONFIG=true eslint ."
|
||||
"test:lint": "eslint .",
|
||||
"test:cli": "pnpm cli"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslegant/cli": "workspace:*",
|
||||
"@fontsource/fira-mono": "^4.5.10",
|
||||
"@neoconfetti/svelte": "^1.0.0",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
|
||||
23
package.json
23
package.json
@@ -1,25 +1,26 @@
|
||||
{
|
||||
"name": "eslit-monorepo",
|
||||
"name": "eslegant-monorepo",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "turbo run lint",
|
||||
"lint:monorepo": "ESLINT_USE_FLAT_CONFIG=true eslint .",
|
||||
"test": "",
|
||||
"lint:monorepo": "eslint .",
|
||||
"test": "echo \"NO TESTS CONFIGURED\"",
|
||||
"release": "changeset publish",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@eslit/core": "workspace:*"
|
||||
"@eslegant/js": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@commitlint/config-conventional": "^17.6.6",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@commitlint/types": "^17.4.4",
|
||||
"eslint": "^8.44.0",
|
||||
"husky": "^8.0.0",
|
||||
"turbo": "^1.10.9"
|
||||
"eslegant": "workspace:*",
|
||||
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
|
||||
"eslint": "^8.47.0",
|
||||
"husky": "^8.0.3",
|
||||
"turbo": "^1.10.12"
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/cli/CHANGELOG.md
Normal file
7
packages/cli/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# @eslit/cli
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Now the cli can automatically detect the workspace structure on monorepos and single repositories ([#10](https://github.com/LoredDev/ESLit/pull/10))
|
||||
17
packages/cli/index.d.ts
vendored
Normal file
17
packages/cli/index.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { CliArgs } from './src/types';
|
||||
|
||||
/**
|
||||
* Class that handles the creation and running the ESLegant command line interface.
|
||||
*/
|
||||
export default class Cli {
|
||||
/**
|
||||
* @param args - Arguments to pass to the cli when its runs.
|
||||
*/
|
||||
public constructor(args: CliArgs);
|
||||
/**
|
||||
* Runs the cli with the given arguments.
|
||||
*/
|
||||
public async run(): Promise<void>;
|
||||
}
|
||||
|
||||
export type { CliArgs, Config } from './src/types.d.ts';
|
||||
1
packages/cli/index.js
Normal file
1
packages/cli/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './src/cli.js';
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": ["./node_modules/**", "./dist/**"],
|
||||
"include": ["./index.d.ts", "./src/**/*.ts", "./src/**/*.js"],
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
}
|
||||
49
packages/cli/package.json
Normal file
49
packages/cli/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@eslegant/cli",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"email": "contact.guz013@gmail.com",
|
||||
"name": "Gustavo \"Guz\" L. de Mello",
|
||||
"url": "https://guz.one"
|
||||
},
|
||||
"module": "./src/index.js",
|
||||
"source": "./src/index.js",
|
||||
"files": [
|
||||
"src",
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"homepage": "https://github.com/LoredDev/ESLegant",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"directory": "packages/config",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LoredDev/ESLegant"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cardinal": "^2.1.1",
|
||||
"commander": "^11.0.0",
|
||||
"nanospinner": "^1.1.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"picomatch": "^2.3.1",
|
||||
"prompts": "^2.4.2",
|
||||
"recast": "^0.23.4",
|
||||
"sisteransi": "^1.0.5",
|
||||
"yaml": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "^1.0.1",
|
||||
"@types/node": "^20.5.3",
|
||||
"@types/prompts": "^2.4.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
193
packages/cli/src/cli.js
Normal file
193
packages/cli/src/cli.js
Normal file
@@ -0,0 +1,193 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable import/max-dependencies */
|
||||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
|
||||
import { createSpinner } from 'nanospinner';
|
||||
import { Command } from 'commander';
|
||||
import { erase } from 'sisteransi';
|
||||
import cardinal from 'cardinal';
|
||||
import prompts from 'prompts';
|
||||
import c from 'picocolors';
|
||||
|
||||
import PackageInstaller from './package-installer.js';
|
||||
import ConfigsProcessor from './configs-processor.js';
|
||||
import ConfigsFile from './configs-file.js';
|
||||
import notNull from './lib/not-null.js';
|
||||
import Workspace from './workspace.js';
|
||||
import count from './lib/count.js';
|
||||
|
||||
const stdout = process.stdout;
|
||||
|
||||
export default class Cli {
|
||||
#program = new Command();
|
||||
|
||||
/** @type {import('./types').CliArgs} */
|
||||
args = {
|
||||
configs: [],
|
||||
dir: process.cwd(),
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('./types').CliArgs} [args] - Cli arguments object.
|
||||
*/
|
||||
constructor(args) {
|
||||
this.#program
|
||||
.argument('[url-to-config]')
|
||||
.option('--packages <string...>')
|
||||
.option('--dir <path>', undefined)
|
||||
.option('--merge-to-root')
|
||||
.option('--install-pkgs')
|
||||
.parse();
|
||||
|
||||
this.args = {
|
||||
...this.args,
|
||||
...this.#program.opts(),
|
||||
...args,
|
||||
};
|
||||
|
||||
this.args.dir = this.args.dir.startsWith('/')
|
||||
? this.args.dir
|
||||
: path.join(process.cwd(), this.args.dir);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function, max-statements, complexity
|
||||
async run() {
|
||||
process.chdir(this.args.dir);
|
||||
|
||||
const configs = this.args.configs;
|
||||
const spinner = createSpinner('Detecting workspace configuration');
|
||||
|
||||
const processor = new ConfigsProcessor({ configs });
|
||||
const workspace = new Workspace(this.args.dir, this.args.packages);
|
||||
|
||||
let packages = await workspace.getPackages();
|
||||
packages = packages.map((pkg) => {
|
||||
spinner.update({
|
||||
text: `Detecting configuration for package ${c.bold(c.blue(pkg.name))}`,
|
||||
});
|
||||
|
||||
pkg.config = processor.detectConfig(pkg);
|
||||
|
||||
return pkg;
|
||||
});
|
||||
|
||||
spinner.success({
|
||||
text:
|
||||
`Detecting workspace configuration ${
|
||||
c.dim(`${count.packagesWithConfigs(packages)} configs founded\n`)}`,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const merge = this.args.mergeToRoot ?? (packages.length > 1
|
||||
/** @type {{merge: boolean}} */
|
||||
? (await prompts({
|
||||
initial: true,
|
||||
message:
|
||||
`Would you like to merge all configuration files into one root ${c.blue('eslint.config.js?')}${
|
||||
c.italic(c.dim('\nAll configurations will be applied to the entire workspace and packages'))}`,
|
||||
name: 'merge',
|
||||
type: 'confirm',
|
||||
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||
})).merge : true);
|
||||
|
||||
console.log(c.dim('\nPlease select which options you prefer\n'));
|
||||
|
||||
packages = await processor.questionConfig(
|
||||
merge ? Workspace.mergePackages(packages) : packages,
|
||||
configs.filter(config => config.manual),
|
||||
);
|
||||
|
||||
const fileHandler = new ConfigsFile(
|
||||
configs,
|
||||
packages.find(config => config.root)?.path,
|
||||
);
|
||||
|
||||
for (const pkg of packages) {
|
||||
pkg.configFile = fileHandler.generateObj(pkg);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
pkg.configFile.content = await ConfigsFile.generate(pkg.configFile);
|
||||
|
||||
/** @type {boolean} */
|
||||
const shouldWrite =
|
||||
/** @type {{write: boolean}} */
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
(await prompts({
|
||||
initial: true,
|
||||
message: `Do you want to write this config file for ${pkg.root
|
||||
? c.blue('the root directory')
|
||||
: c.blue(pkg.name)
|
||||
}?\n\n${cardinal.highlight(pkg.configFile.content)}`,
|
||||
name: 'write',
|
||||
type: 'confirm',
|
||||
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||
})).write;
|
||||
|
||||
stdout.write(
|
||||
erase.lines(pkg.configFile.content.split('\n').length + 2),
|
||||
);
|
||||
|
||||
if (shouldWrite) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await ConfigsFile.write(
|
||||
pkg.configFile.path,
|
||||
pkg.configFile.content,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const packagesMap = new Map(packages.map(p =>
|
||||
[p.path, [...notNull(p.configFile).imports.keys()]],
|
||||
));
|
||||
const installer = new PackageInstaller(
|
||||
packagesMap,
|
||||
packages.find(p => p.root === true)?.path ?? this.args.dir,
|
||||
);
|
||||
|
||||
/** @type {boolean | 'changePackage'} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
let installPkgs = this.args.installPkgs ?? (await prompts({
|
||||
choices: [
|
||||
{
|
||||
description: installer.packageManager.description,
|
||||
title: 'Yes, install all packages',
|
||||
value: true,
|
||||
},
|
||||
{ title: 'No, I will install them manually', value: false },
|
||||
{ title: 'Change package manager', value: 'changePackage' },
|
||||
],
|
||||
message:
|
||||
`Would you like to ESLit to install the npm packages with ${c.green(installer.packageManager.name)}?\n${c.reset(c.dim(` Packages to install: ${[...new Set(packagesMap.values())].join(' ')}\n`))}`,
|
||||
name: 'install',
|
||||
type: 'select',
|
||||
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||
})).install;
|
||||
|
||||
if (installPkgs === 'changePackage') {
|
||||
/** @type {{manager: import('./types').PackageManagerName}} */
|
||||
const prompt = await prompts({
|
||||
choices: Object.values(installer.packageManagers).map(m => ({
|
||||
description: m.description,
|
||||
title: m.name,
|
||||
value: m.name,
|
||||
})),
|
||||
message: 'What package manager do you want ESLit to use?',
|
||||
name: 'manager',
|
||||
type: 'select',
|
||||
});
|
||||
installer.packageManager =
|
||||
installer.packageManagers[prompt.manager];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!installer.packageManager)
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal, @typescript-eslint/no-confusing-void-expression
|
||||
throw console.log(c.red('You must select a package manager'));
|
||||
|
||||
installPkgs = true;
|
||||
}
|
||||
|
||||
if (installPkgs) await installer.install();
|
||||
}
|
||||
}
|
||||
|
||||
407
packages/cli/src/configs-file.js
Normal file
407
packages/cli/src/configs-file.js
Normal file
@@ -0,0 +1,407 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import process from 'node:process';
|
||||
import fs from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { parse, prettyPrint } from 'recast';
|
||||
|
||||
import astUtils from './lib/ast-utils.js';
|
||||
import notNull from './lib/not-null.js';
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').ConfigFile['imports']} map1 -
|
||||
* The map to has it values merged from map2.
|
||||
* @param {import('./types.js').ConfigFile['imports']} map2 -
|
||||
* The map to has it values merged to map1.
|
||||
* @returns {import('./types.js').ConfigFile['imports']} The resulting map.
|
||||
*/
|
||||
// eslint-disable-next-line max-statements
|
||||
function mergeImportsMaps(map1, map2) {
|
||||
for (const [key, value] of map2) {
|
||||
if (!map1.has(key)) {
|
||||
map1.set(key, value);
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
const imports1 = notNull(map1.get(key));
|
||||
const imports2 = notNull(map2.get(key));
|
||||
|
||||
/**
|
||||
* Because arrays and objects are always different independently from having equal values
|
||||
* ([] === [] -> false). It is converted to a string so the comparison can be made.
|
||||
*/
|
||||
switch ([
|
||||
typeof imports1 === 'string',
|
||||
typeof imports2 === 'string',
|
||||
].join(',')) {
|
||||
case 'true,true': {
|
||||
if (imports1.toString() === imports2.toString()) {
|
||||
map1.set(key, value);
|
||||
}
|
||||
else {
|
||||
map1.set(key, [
|
||||
['default', imports1.toString()],
|
||||
['default', imports2.toString()],
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'true,false': {
|
||||
map1.set(key, [['default', imports1.toString()], ...imports2]);
|
||||
break;
|
||||
}
|
||||
case 'false,true': {
|
||||
map1.set(key, [['default', imports2.toString()], ...imports1]);
|
||||
break;
|
||||
}
|
||||
case 'false,false': {
|
||||
map1.set(key, [...imports1, ...imports2]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// No nothing
|
||||
}
|
||||
if (typeof map1.get(key) !== 'string')
|
||||
map1.set(key, [...new Set(map1.get(key))]);
|
||||
}
|
||||
return map1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path1 - The path to traverse from.
|
||||
* @param {string} root - The root path to be removed from the path1.
|
||||
* @returns {string} The path to traverse.
|
||||
*/
|
||||
function getPathDepth(path1, root) {
|
||||
const pathDepth = path1.replace(root, '').split('/').slice(1);
|
||||
if (pathDepth.length <= 1) return pathDepth.map(() => '.').join('/');
|
||||
return pathDepth.map(() => '..').join('/');
|
||||
}
|
||||
|
||||
export default class ConfigsWriter {
|
||||
/** @type {string} */
|
||||
root = process.cwd();
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').Config[]} configs - The array of configs to construct from.
|
||||
* @param {string} [root] - The root directory path.
|
||||
*/
|
||||
constructor(configs, root) {
|
||||
this.configs = configs;
|
||||
this.root = root ?? this.root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} ast - The program ast to be manipulated.
|
||||
* @returns {Promise<Program>} The final ast with the recreated default export.
|
||||
* @private
|
||||
*/
|
||||
static async addDefaultExport(ast) {
|
||||
/** @type {{program: Program}} */
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
const { program: exportTemplateAst } = parse([
|
||||
'/** @type {import(\'eslint\').Linter.FlatConfig[]} */',
|
||||
'export default [',
|
||||
'',
|
||||
'];',
|
||||
].join('\n'), { parser: (await import('recast/parsers/babel.js')) });
|
||||
/** @type {import('estree').ExportDefaultDeclaration} */
|
||||
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
||||
const exportTemplateNode = exportTemplateAst.body
|
||||
.find(n => n.type === 'ExportDefaultDeclaration');
|
||||
|
||||
/** @type {import('estree').ExportDefaultDeclaration | undefined} */
|
||||
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
||||
const astExport = ast.body
|
||||
.find(n => n.type === 'ExportDefaultDeclaration');
|
||||
|
||||
if (!astExport) { ast.body.push(exportTemplateNode); return ast; }
|
||||
|
||||
/** @type {import('estree').VariableDeclaration | undefined} */
|
||||
const oldExportValue = astExport.declaration.type === 'ArrayExpression'
|
||||
? undefined
|
||||
: astUtils.createVariable(
|
||||
'oldConfig',
|
||||
// @ts-expect-error astExport.declaration is a expression
|
||||
astExport.declaration,
|
||||
'const',
|
||||
);
|
||||
|
||||
if (!oldExportValue) return ast;
|
||||
|
||||
// @ts-expect-error declaration is a ArrayExpression
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
exportTemplateNode.declaration.elements.push({
|
||||
argument: { name: 'oldConfig', type: 'Identifier' },
|
||||
type: 'SpreadElement',
|
||||
});
|
||||
|
||||
const astExportIdx = ast.body.indexOf(astExport);
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
ast.body[astExportIdx] = exportTemplateNode;
|
||||
ast.body.splice(astExportIdx - 1, 0, oldExportValue);
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* ! NOTE:
|
||||
* These functions declared bellow are notably hard to read and have lots of exceptions and
|
||||
* disabled eslint and typescript checks. Unfortunately this is something that I wasn't able to
|
||||
* prevent because a lot of the AST typescript types are somewhat wrong or simply hard to work
|
||||
* with them.
|
||||
*
|
||||
* But for somewhat help developing and prevent unwanted errors in the future, the types and
|
||||
* eslint errors are explicitly disabled and types are explicitly overridden. This is why
|
||||
* there are so many JSDoc type annotations and comments in general.
|
||||
*
|
||||
* Any help to make this code more readable and robust is appreciated.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('estree').Program} Program
|
||||
* @typedef {(
|
||||
* import('./lib/ast-utils.js').ExpressionOrIdentifier |
|
||||
* import('estree').ObjectExpression |
|
||||
* import('estree').SpreadElement
|
||||
* )} ConfigArrayElement
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds elements to the default export node, without adding duplicates.
|
||||
* @typedef {import('estree').ArrayExpression} ArrayExpression
|
||||
* @param {Program} ast - The program ast to be manipulated.
|
||||
* @param {ConfigArrayElement[]} elements - The elements to be added to the array.
|
||||
* @returns {Program} The final ast with the recreated default export.
|
||||
* @private
|
||||
*/
|
||||
static addElementsToExport(ast, elements) {
|
||||
/** @type {import('estree').ExportDefaultDeclaration} */
|
||||
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
||||
const exportNode = ast.body.find(n =>
|
||||
n.type === 'ExportDefaultDeclaration',
|
||||
);
|
||||
const exportNodeIdx = ast.body.indexOf(exportNode);
|
||||
|
||||
/** @type {ArrayExpression} */
|
||||
// @ts-expect-error declaration is a ArrayExpression
|
||||
const array = exportNode.declaration;
|
||||
|
||||
for (const e of elements) {
|
||||
if (!(
|
||||
e.type !== 'ObjectExpression' &&
|
||||
astUtils.findInArray(array, e)
|
||||
))
|
||||
array.elements.push(e);
|
||||
}
|
||||
|
||||
exportNode.declaration = array;
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
ast.body[exportNodeIdx] = exportNode;
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} ast - The program ast to be manipulated.
|
||||
* @param {import('./types.js').ConfigFile['imports']} imports - The imports map to be used.
|
||||
* @returns {Program} The final ast with the recreated default export.
|
||||
*/
|
||||
static addPackageImports(ast, imports) {
|
||||
/** @type {import('estree').ImportDeclaration[]} */
|
||||
const importDeclarations = [];
|
||||
|
||||
for (const [pkgName, specifiers] of imports) {
|
||||
/** @type {import('estree').ImportDeclaration | undefined} */
|
||||
// @ts-expect-error type error, the specifier has to be ImportDeclaration to be founded
|
||||
const existingDeclaration = ast.body.find(s =>
|
||||
s.type === 'ImportDeclaration' &&
|
||||
s.source.value === pkgName,
|
||||
);
|
||||
|
||||
const importDeclaration = astUtils.createImportDeclaration(
|
||||
pkgName,
|
||||
typeof specifiers === 'string'
|
||||
? specifiers
|
||||
: undefined,
|
||||
existingDeclaration,
|
||||
);
|
||||
|
||||
if (typeof specifiers !== 'string') {
|
||||
for (const s of specifiers) {
|
||||
if (typeof s === 'string')
|
||||
importDeclaration.addSpecifier(s);
|
||||
else
|
||||
importDeclaration.addSpecifier(s[0], s[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (existingDeclaration) {
|
||||
ast.body[ast.body.indexOf(existingDeclaration)] =
|
||||
importDeclaration.body;
|
||||
}
|
||||
else {
|
||||
importDeclarations.push(importDeclaration.body);
|
||||
}
|
||||
}
|
||||
|
||||
ast.body.unshift(...importDeclarations);
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').ConfigFile['rules']} rules -
|
||||
* The rules to be used to create the object.
|
||||
* @returns {import('estree').ObjectExpression} The object containing the spread rules.
|
||||
* @private
|
||||
*/
|
||||
static createRulesObject(rules) {
|
||||
/** @type {import('estree').SpreadElement[]} */
|
||||
// @ts-expect-error The array is filtered to remove undefined's
|
||||
const expressions = rules
|
||||
.map((r) => {
|
||||
const e = astUtils.stringToExpression(r);
|
||||
return e ? astUtils.toSpreadElement(e) : undefined;
|
||||
}).filter(Boolean);
|
||||
|
||||
return {
|
||||
properties: [{
|
||||
key: { name: 'rules', type: 'Identifier' },
|
||||
// @ts-expect-error because ObjectProperty doesn't exist in estree types
|
||||
type: 'ObjectProperty',
|
||||
value: {
|
||||
properties: expressions,
|
||||
type: 'ObjectExpression',
|
||||
},
|
||||
}],
|
||||
type: 'ObjectExpression',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').ConfigFile} config -
|
||||
* The config file object to be transformed into a eslint.config.js file.
|
||||
* @returns {Promise<string>} The generated config file contents.
|
||||
*/
|
||||
static async generate(config) {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
const existingConfig = existsSync(config.path)
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
? await fs.readFile(config.path, 'utf8')
|
||||
: '';
|
||||
|
||||
/** @type {{program: Program}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { program: ast } = parse(
|
||||
existingConfig,
|
||||
{ parser: (await import('recast/parsers/babel.js')) },
|
||||
);
|
||||
|
||||
await ConfigsWriter.addDefaultExport(ast);
|
||||
|
||||
/**
|
||||
* @type {ConfigArrayElement[]}
|
||||
*/
|
||||
// @ts-expect-error The array is filtered to remove undefined's
|
||||
const elements = [
|
||||
...config.configs.map(c => astUtils.stringToExpression(c)),
|
||||
...config.presets.map((p) => {
|
||||
const e = astUtils.stringToExpression(p);
|
||||
return e ? astUtils.toSpreadElement(e) : undefined;
|
||||
}),
|
||||
config.rules.length > 0
|
||||
? ConfigsWriter.createRulesObject(config.rules)
|
||||
: undefined,
|
||||
].filter(Boolean);
|
||||
|
||||
ConfigsWriter.addElementsToExport(ast, elements);
|
||||
ConfigsWriter.addPackageImports(ast, config.imports);
|
||||
|
||||
const finalCode = prettyPrint(
|
||||
ast,
|
||||
{ parser: (await import('recast/parsers/babel.js')) },
|
||||
).code;
|
||||
return finalCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').Package} pkg - The package to generate the config string from.
|
||||
* @returns {import('./types.js').ConfigFile} The config file object.
|
||||
*/
|
||||
// eslint-disable-next-line max-statements
|
||||
generateObj(pkg) {
|
||||
/** @type {import('./types.js').ConfigFile} */
|
||||
const configObj = {
|
||||
configs: [],
|
||||
imports: new Map(),
|
||||
path: join(pkg.path, 'eslint.config.js'),
|
||||
presets: [],
|
||||
rules: [],
|
||||
};
|
||||
|
||||
if (!pkg.root) {
|
||||
const rootConfig = join(
|
||||
getPathDepth(pkg.path, this.root),
|
||||
'eslint.config.js',
|
||||
);
|
||||
configObj.imports.set(rootConfig.startsWith('.') ? rootConfig : `./${rootConfig}`, 'root');
|
||||
configObj.presets.push('root');
|
||||
}
|
||||
|
||||
for (const [configName, optionsNames] of notNull(pkg.config)) {
|
||||
const config = this.configs.find(c => c.name === configName);
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!config) continue;
|
||||
|
||||
const options = config.options.filter(o =>
|
||||
optionsNames.includes(o.name),
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-continue
|
||||
if (!options || options.length === 0) continue;
|
||||
|
||||
// eslint-disable-next-line unicorn/no-array-reduce
|
||||
const imports = options.reduce((acc, opt) => {
|
||||
const map1 = new Map(Object.entries(acc.packages ?? {}));
|
||||
const map2 = new Map(Object.entries(opt.packages ?? {}));
|
||||
acc.packages = Object.fromEntries(mergeImportsMaps(map1, map2));
|
||||
return acc;
|
||||
});
|
||||
|
||||
configObj.imports = mergeImportsMaps(
|
||||
configObj.imports,
|
||||
new Map(Object.entries(imports.packages ?? {})),
|
||||
);
|
||||
|
||||
configObj.configs = [...new Set([
|
||||
...configObj.configs,
|
||||
...options.flatMap(o => o.configs ?? []),
|
||||
])];
|
||||
|
||||
configObj.rules = [...new Set([
|
||||
...configObj.rules,
|
||||
...options.flatMap(o => o.rules ?? []),
|
||||
])];
|
||||
|
||||
configObj.presets = [...new Set([
|
||||
...configObj.presets,
|
||||
...options.flatMap(o => o.presets ?? []),
|
||||
])];
|
||||
}
|
||||
|
||||
return configObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path - The path to the file to be written.
|
||||
* @param {string} content - The content of the file.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async write(path, content) {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
await fs.writeFile(path, content, 'utf8');
|
||||
}
|
||||
}
|
||||
176
packages/cli/src/configs-processor.js
Executable file
176
packages/cli/src/configs-processor.js
Executable file
@@ -0,0 +1,176 @@
|
||||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
|
||||
import prompts from 'prompts';
|
||||
import glob from 'picomatch';
|
||||
import c from 'picocolors';
|
||||
|
||||
import capitalize from './lib/capitalize.js';
|
||||
|
||||
export default class ConfigsProcessor {
|
||||
/** @type {import('./types.js').Config[]} */
|
||||
configs;
|
||||
|
||||
/** @type {string} */
|
||||
dir = process.cwd();
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* configs: import('./types.js').Config[],
|
||||
* packages?: string[],
|
||||
* directory?: string,
|
||||
* }} options - Cli options.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.configs = options.configs;
|
||||
this.dir = path.normalize(options.directory ?? this.dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').Package} pkg - The package to detect configs.
|
||||
* @returns {import('./types.js').Package['config']} - Detected configs record.
|
||||
*/
|
||||
detectConfig(pkg) {
|
||||
/** @type {import('./types.js').Package['config']} */
|
||||
const pkgConfig = new Map();
|
||||
|
||||
for (const config of this.configs.filter(cfg => !cfg.manual)) {
|
||||
pkgConfig.set(config.name, ConfigsProcessor.detectOptions(
|
||||
pkg,
|
||||
config.options,
|
||||
config.type !== 'multiple',
|
||||
));
|
||||
}
|
||||
|
||||
return pkgConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').Package} pkg - Package to detect from.
|
||||
* @param {import('./types.js').Config['options']} options - Options to be passed.
|
||||
* @param {boolean} single - Whether to only detect one option.
|
||||
* @returns {string[]} - The detected options.
|
||||
*/
|
||||
static detectOptions(pkg, options, single) {
|
||||
/** @type {string[]} */
|
||||
const detectedOptions = [];
|
||||
|
||||
for (const option of options) {
|
||||
if (option.detect === true) {
|
||||
detectedOptions.push(option.name);
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
// eslint-disable-next-line no-continue
|
||||
else if (!option.detect) { continue; }
|
||||
|
||||
const match = glob(option.detect);
|
||||
|
||||
const files = pkg.files.filter(f => (match ? match(f) : false));
|
||||
const directories = pkg.directories.filter(f =>
|
||||
(match ? match(f) : false),
|
||||
);
|
||||
|
||||
if (files.length > 0 || directories.length > 0) {
|
||||
detectedOptions.push(option.name);
|
||||
if (single) break;
|
||||
}
|
||||
}
|
||||
|
||||
return detectedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types.js').Package[] | import('./types.js').Package} pkg -
|
||||
* The packages to questions the configs.
|
||||
* @param {import('./types.js').Config[]} configs - The configs to be used.
|
||||
* @returns {Promise<import('./types.js').Package[]>} - The selected options by the user.
|
||||
*/
|
||||
// eslint-disable-next-line max-statements, complexity, max-lines-per-function
|
||||
async questionConfig(pkg, configs) {
|
||||
const packages = Array.isArray(pkg) ? [...pkg] : [pkg];
|
||||
|
||||
const instructions = c.dim(`\n${c.bold('A: Toggle all')} - ↑/↓: Highlight option - ←/→/[space]: Toggle selection - enter/return: Complete answer`);
|
||||
|
||||
for (const config of configs) {
|
||||
/** @type {import('prompts').Choice[]} */
|
||||
const configChoices = config.options.map(option => ({ title: `${capitalize(option.name)}`, value: option.name }));
|
||||
|
||||
/** @type {Record<string, string[]>} */
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const selectedOptions = await prompts({
|
||||
choices: config.type === 'confirm' ? [
|
||||
{
|
||||
title: 'Yes',
|
||||
value: ['yes'],
|
||||
},
|
||||
{
|
||||
title: 'No',
|
||||
value: null,
|
||||
},
|
||||
] : configChoices,
|
||||
hint: config.description,
|
||||
instructions: instructions + c.dim(c.italic(
|
||||
// eslint-disable-next-line max-len
|
||||
'\nSelect none if you don\'t want to use this configuration\n',
|
||||
)),
|
||||
message: capitalize(config.name),
|
||||
name: config.name,
|
||||
type: config.type === 'multiple' ? 'multiselect' : 'select',
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-continue, @typescript-eslint/no-unnecessary-condition
|
||||
if (!selectedOptions[config.name]) continue;
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (selectedOptions[config.name].length === 0) continue;
|
||||
|
||||
if (packages.length <= 1) {
|
||||
packages[0].config = new Map([
|
||||
...(packages[0].config ?? []),
|
||||
...Object.entries(selectedOptions),
|
||||
]);
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @type {{title: string, value: import('./types.js').Package}[]} */
|
||||
const packagesOptions = packages
|
||||
.map(p => (p.root
|
||||
? { title: 'root', value: p }
|
||||
: {
|
||||
title: `${p.name} ${c.dim(p.path.replace(this.dir, '.'))}`,
|
||||
value: p,
|
||||
}))
|
||||
.filter(p => p.title !== 'root');
|
||||
|
||||
/** @type {Record<'packages', import('./types.js').Package[]>} */
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const selected = await prompts({
|
||||
choices: packagesOptions,
|
||||
instructions: instructions + c.dim(c.italic(
|
||||
'\nToggle all to use in the root configuration\n',
|
||||
)),
|
||||
message: `What packages would you like to apply ${config.type === 'single' ? 'this choice' : 'these choices'}?`,
|
||||
min: 1,
|
||||
name: 'packages',
|
||||
type: 'autocompleteMultiselect',
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
selected.packages ??= [];
|
||||
|
||||
selected.packages.map((p) => {
|
||||
p.config = new Map([
|
||||
...(p.config ?? []),
|
||||
...Object.entries(selectedOptions),
|
||||
]); return p;
|
||||
});
|
||||
packages.map(p =>
|
||||
selected.packages.find(s => s.name === p.name) ?? p,
|
||||
);
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
}
|
||||
|
||||
190
packages/cli/src/lib/ast-utils.js
Normal file
190
packages/cli/src/lib/ast-utils.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import { parse, print } from 'recast';
|
||||
|
||||
/**
|
||||
* @typedef {(
|
||||
* import('estree').MemberExpression |
|
||||
* import('estree').Identifier |
|
||||
* import('estree').CallExpression |
|
||||
* import('estree').NewExpression
|
||||
* )} ExpressionOrIdentifier
|
||||
* This type only includes the expressions used in the cli's config type
|
||||
* @typedef {import('estree').VariableDeclaration} VariableDeclaration
|
||||
* @typedef {import('estree').Identifier['name']} IdentifierName
|
||||
* @typedef {VariableDeclaration['kind']} VariableKind
|
||||
* @typedef {import('estree').VariableDeclarator['init']} VariableInit
|
||||
* @typedef {import('estree').SpreadElement} SpreadElement
|
||||
* @typedef {import('estree').Expression} Expression
|
||||
* @typedef {import('estree').ArrayExpression} ArrayExpression
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {IdentifierName} identifier - Nave of the variable identifier.
|
||||
* @param {VariableInit} [init] - Initial value of the variable.
|
||||
* @param {VariableKind} [kind] - Type of variable declaration.
|
||||
* @returns {VariableDeclaration} The variable declaration ast node object.
|
||||
*/
|
||||
function createVariable(identifier, init, kind = 'const') {
|
||||
return {
|
||||
declarations: [{
|
||||
id: { name: identifier, type: 'Identifier' },
|
||||
init,
|
||||
type: 'VariableDeclarator',
|
||||
}],
|
||||
kind,
|
||||
type: 'VariableDeclaration',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string - The code/expression in string.
|
||||
* @returns {ExpressionOrIdentifier | undefined} -
|
||||
* The expression or identifier node of that string (undefined if string is not a expression).
|
||||
*/
|
||||
function stringToExpression(string) {
|
||||
/** @type {ExpressionOrIdentifier} */
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
const e = parse(string).program.body[0].expression;
|
||||
if ([
|
||||
'CallExpression',
|
||||
'Identifier',
|
||||
'MemberExpression',
|
||||
'NewExpression',
|
||||
].includes(e.type)) return e;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayExpression} array - The array node to search trough.
|
||||
* @param {ExpressionOrIdentifier | SpreadElement} element - The element to be search.
|
||||
* @returns {ExpressionOrIdentifier | undefined}
|
||||
* The element of the array founded, undefined if it isn't found.
|
||||
*/
|
||||
function findInArray(array, element) {
|
||||
/** @type {ExpressionOrIdentifier[]} */
|
||||
// @ts-expect-error The array should have just tge type above
|
||||
element = element.type === 'SpreadElement' ? element.argument : element;
|
||||
|
||||
/** @type {ExpressionOrIdentifier[]} */
|
||||
// @ts-expect-error The array is filtered to have the type above
|
||||
const filteredElements = array.elements
|
||||
.map((n) => {
|
||||
if (n?.type === 'SpreadElement') return n.argument;
|
||||
return n;
|
||||
}).filter(n => n && n.type === element.type);
|
||||
|
||||
const toStringElements = filteredElements.map(n => print(n).code);
|
||||
const toStringElement = print(element).code;
|
||||
|
||||
const idx = toStringElements.indexOf(toStringElement);
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
return filteredElements[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ExpressionOrIdentifier} expression - The expression to be spread.
|
||||
* @returns {SpreadElement} The spread element node.
|
||||
*/
|
||||
function toSpreadElement(expression) {
|
||||
return {
|
||||
argument: expression,
|
||||
type: 'SpreadElement',
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-secrets/no-secrets
|
||||
/**
|
||||
* @typedef {{
|
||||
* body: import('estree').ImportDeclaration
|
||||
* addSpecifier: (specifier: string, alias?: string) => ThisType<ImportDeclarationHelper>
|
||||
* convertDefaultSpecifier: () => ThisType<ImportDeclarationHelper>
|
||||
* }} ImportDeclarationHelper
|
||||
* @param {string} source - The package name or source path to be imported.
|
||||
* @param {string} [defaultImported] - The default specifier imported.
|
||||
* @param {import('estree').ImportDeclaration} [body] -
|
||||
* The body of the import declaration to start with.
|
||||
* @returns {ImportDeclarationHelper} A helper object for manipulating the import declaration.
|
||||
*/
|
||||
function createImportDeclaration(source, defaultImported, body) {
|
||||
const helper = {
|
||||
/**
|
||||
* @param {string} specifier - The value to be imported from the package.
|
||||
* @param {string} [alias] - The local alias of the value.
|
||||
* @returns {ThisType<ImportDeclarationHelper>} This helper with the added specifiers.
|
||||
*/
|
||||
addSpecifier(specifier, alias) {
|
||||
this.convertDefaultSpecifier();
|
||||
if (this.body.specifiers.some(s =>
|
||||
s.local.name === alias || s.local.name === specifier,
|
||||
))
|
||||
return this;
|
||||
|
||||
this.body.specifiers.push({
|
||||
imported: {
|
||||
name: specifier,
|
||||
type: 'Identifier',
|
||||
},
|
||||
local: {
|
||||
name: alias ?? specifier,
|
||||
type: 'Identifier',
|
||||
},
|
||||
type: 'ImportSpecifier',
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
/** @type {import('estree').ImportDeclaration} */
|
||||
body: body ?? {
|
||||
source: {
|
||||
type: 'Literal',
|
||||
value: source,
|
||||
},
|
||||
specifiers: defaultImported ? [{
|
||||
local: { name: defaultImported, type: 'Identifier' },
|
||||
type: 'ImportDefaultSpecifier',
|
||||
}] : [],
|
||||
type: 'ImportDeclaration',
|
||||
},
|
||||
/**
|
||||
* Converts a default specifier to a specifier with a alias.
|
||||
* @returns {ThisType<ImportDeclarationHelper>} -
|
||||
* This helper with the converted default specifier.
|
||||
* @example
|
||||
* import eslit from 'eslit';
|
||||
* // Is converted to
|
||||
* import { default as eslit } from 'eslit';
|
||||
*/
|
||||
convertDefaultSpecifier() {
|
||||
const specifier = this.body.specifiers.find(s =>
|
||||
s.type === 'ImportDefaultSpecifier',
|
||||
);
|
||||
if (!specifier)
|
||||
return this;
|
||||
|
||||
this.body.specifiers.splice(
|
||||
this.body.specifiers.indexOf(specifier),
|
||||
1,
|
||||
);
|
||||
return this.addSpecifier('default', specifier.local.name);
|
||||
},
|
||||
};
|
||||
|
||||
if (defaultImported &&
|
||||
body &&
|
||||
!body.specifiers.some(s =>
|
||||
s.type === 'ImportDefaultSpecifier' &&
|
||||
s.local.name === defaultImported,
|
||||
))
|
||||
helper.addSpecifier('default', defaultImported);
|
||||
|
||||
return helper;
|
||||
}
|
||||
|
||||
const astUtils = {
|
||||
createImportDeclaration,
|
||||
createVariable,
|
||||
findInArray,
|
||||
stringToExpression,
|
||||
toSpreadElement,
|
||||
};
|
||||
export default astUtils;
|
||||
8
packages/cli/src/lib/capitalize.js
Normal file
8
packages/cli/src/lib/capitalize.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
/**
|
||||
* @param {string} str - The string to capitalize.
|
||||
* @returns {string} The capitalized string.
|
||||
*/
|
||||
export default function capitalize(str) {
|
||||
return str[0].toUpperCase() + str.slice(1);
|
||||
}
|
||||
14
packages/cli/src/lib/count.js
Normal file
14
packages/cli/src/lib/count.js
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
/**
|
||||
* @param {import('../types').Package[]} packages - Package list.
|
||||
* @returns {number} Number of packages' configs.
|
||||
*/
|
||||
function packagesWithConfigs(packages) {
|
||||
return packages.map(p =>
|
||||
[...p.config?.values() ?? []]
|
||||
.filter(options => options.length > 0).length,
|
||||
).reduce((partial, sum) => partial + sum, 0);
|
||||
}
|
||||
|
||||
const count = { packagesWithConfigs };
|
||||
export default count;
|
||||
17
packages/cli/src/lib/not-null.js
Normal file
17
packages/cli/src/lib/not-null.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// eslint-disable-next-line no-secrets/no-secrets
|
||||
/**
|
||||
* JSDoc types lack a non-null assertion.
|
||||
* @template T
|
||||
* @param {T} value - The value which to assert against null or undefined.
|
||||
* @returns {NonNullable<T>} The said value.
|
||||
* @throws {TypeError} If the value is unexpectedly null or undefined.
|
||||
* @see https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-873331031
|
||||
* @see https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-1249287966
|
||||
* @author Jimmy Wärting - https://github.com/jimmywarting
|
||||
*/
|
||||
export default function notNull(value) {
|
||||
// Use `==` to check for both null and undefined
|
||||
if (value === null || value === undefined)
|
||||
throw new Error('did not expect value to be null or undefined');
|
||||
return value;
|
||||
}
|
||||
237
packages/cli/src/package-installer.js
Normal file
237
packages/cli/src/package-installer.js
Normal file
@@ -0,0 +1,237 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { exec } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { parse, prettyPrint } from 'recast';
|
||||
import { createSpinner } from 'nanospinner';
|
||||
import c from 'picocolors';
|
||||
|
||||
|
||||
/**
|
||||
* @type {import('./types').PackageManagerHandler}
|
||||
*/
|
||||
class CommandHandler {
|
||||
/** @type {((path: string, packages: string[]) => string | Promise<string>) | undefined} */
|
||||
checker;
|
||||
|
||||
/** @type {string} */
|
||||
command;
|
||||
|
||||
/**
|
||||
* @param {string} command - What command to use to install.
|
||||
* @param {(path: string, packages: string[]) => string | Promise<string>} [checker] -
|
||||
* Checks if a argument should be passed.
|
||||
*/
|
||||
constructor(command, checker) {
|
||||
this.command = command;
|
||||
this.checker = checker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path - The path to run the command.
|
||||
* @param {string[]} packages - The packages to be added on the command.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async install(path, packages) {
|
||||
if (this.checker)
|
||||
this.command += await this.checker(path, packages);
|
||||
|
||||
return new Promise((res) => {
|
||||
const spinner = createSpinner(`Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}`).start();
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-child-process
|
||||
const child = exec(`${this.command} ${packages.join(' ')}`, { cwd: path });
|
||||
child.stdout?.on('data', chunk => spinner.update({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
text: `Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}\n ${c.dim(chunk)}`,
|
||||
}));
|
||||
child.stdout?.on('close', () => {
|
||||
spinner.success({
|
||||
text: `Installed packages with ${c.green(this.command)}`,
|
||||
}); res();
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-confusing-void-expression
|
||||
res(console.error(`Error while installing the packages with ${this.command} ${c.dim(packages.join(' '))} on ${path}: ${error}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('./types').PackageManagerHandler}
|
||||
*/
|
||||
const DenoHandler = {
|
||||
/**
|
||||
* @param {string} path - The path to run the command.
|
||||
* @param {string[]} packages - The packages to be added on the command.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async install(path, packages) {
|
||||
const configPath = join(path, 'eslint.config.js');
|
||||
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
if (!existsSync(configPath)) return;
|
||||
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
const configFile = await readFile(configPath, 'utf8');
|
||||
/** @type {{program: import('estree').Program}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { program: ast } = parse(
|
||||
configFile,
|
||||
{ parser: (await import('recast/parsers/babel.js')) },
|
||||
);
|
||||
|
||||
ast.body.map((node) => {
|
||||
if (node.type !== 'ImportDeclaration') return node;
|
||||
|
||||
if (packages.includes(node.source.value?.toString() ?? ''))
|
||||
node.source.value = `npm:${node.source.value}`;
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
await writeFile(configPath, prettyPrint(ast).code, 'utf8');
|
||||
|
||||
console.log(c.green('Added npm: specifier to dependencies'));
|
||||
},
|
||||
};
|
||||
|
||||
export default class PackageInstaller {
|
||||
/**
|
||||
* @typedef {{
|
||||
* name: import('./types').PackageManagerName
|
||||
* description: string
|
||||
* handler: import('./types').PackageManagerHandler
|
||||
* }} PackageManager
|
||||
* @type {PackageManager}
|
||||
*/
|
||||
packageManager;
|
||||
|
||||
/**
|
||||
* @type {Record<import('./types').PackageManagerName, PackageManager>}
|
||||
*/
|
||||
packageManagers = {
|
||||
bun: {
|
||||
description: 'Uses bun install',
|
||||
handler: new CommandHandler('bun install'),
|
||||
name: 'bun',
|
||||
},
|
||||
deno: {
|
||||
description: 'Adds npm: specifiers to the eslint.config.js file',
|
||||
handler: DenoHandler,
|
||||
name: 'deno',
|
||||
},
|
||||
npm: {
|
||||
description: 'Uses npm install',
|
||||
handler: new CommandHandler('npm install --save-dev'),
|
||||
name: 'npm',
|
||||
},
|
||||
pnpm: {
|
||||
description: 'Uses pnpm install',
|
||||
handler: new CommandHandler('pnpm install --save-dev', (path) => {
|
||||
if (
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
existsSync(join(path, 'pnpm-workspace.yaml')) &&
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
existsSync(join(path, 'package.json'))
|
||||
)
|
||||
return ' -w';
|
||||
return '';
|
||||
}),
|
||||
name: 'pnpm',
|
||||
},
|
||||
yarn: {
|
||||
description: 'Uses yarn add',
|
||||
handler: new CommandHandler('yarn add --dev'),
|
||||
name: 'yarn',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Map<string, string[]>} PackagesMap
|
||||
* @type {PackagesMap}
|
||||
*/
|
||||
packagesMap;
|
||||
|
||||
/**
|
||||
* @param {PackagesMap} packagesMap - The map of directories and packages to be installed.
|
||||
* @param {string} root - Root directory path.
|
||||
*/
|
||||
constructor(packagesMap, root) {
|
||||
this.packagesMap = packagesMap;
|
||||
this.packageManager = this.detectPackageManager(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} root - Root directory path.
|
||||
* @returns {PackageManager} The package manager detected;.
|
||||
* @private
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
detectPackageManager(root) {
|
||||
/** @type {(...path: string[]) => boolean} */
|
||||
function exists(...path) {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
return existsSync(join(root, ...path));
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case exists('deno.json'):
|
||||
case exists('deno.jsonc'): {
|
||||
return this.packageManagers.deno;
|
||||
}
|
||||
|
||||
case exists('bun.lockb'): {
|
||||
return this.packageManagers.bun;
|
||||
}
|
||||
|
||||
case exists('pnpm-lock.yaml'): {
|
||||
return this.packageManagers.pnpm;
|
||||
}
|
||||
|
||||
case exists('yarn.lock'): {
|
||||
return this.packageManagers.yarn;
|
||||
}
|
||||
|
||||
case exists('package-lock.json'): {
|
||||
return this.packageManagers.npm;
|
||||
}
|
||||
|
||||
case exists('package.json'): {
|
||||
/** @type {{packageManager?: string}} */
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-case-declarations
|
||||
const { packageManager } = JSON.parse(
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
readFileSync(join(root, 'package.json'), 'utf8'),
|
||||
);
|
||||
if (!packageManager) return this.packageManagers.npm;
|
||||
|
||||
if (packageManager.includes('pnpm'))
|
||||
return this.packageManagers.pnpm;
|
||||
if (packageManager.includes('yarn'))
|
||||
return this.packageManagers.yarn;
|
||||
if (packageManager.includes('npm'))
|
||||
return this.packageManagers.npm;
|
||||
|
||||
return this.packageManagers.npm;
|
||||
}
|
||||
|
||||
default: { return this.packageManagers.npm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async install() {
|
||||
for (const [path, packages] of this.packagesMap)
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this.packageManager.handler.install(path, packages);
|
||||
}
|
||||
}
|
||||
72
packages/cli/src/types.d.ts
vendored
Normal file
72
packages/cli/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { OptionValues } from 'commander';
|
||||
|
||||
type PackageManagerName = 'bun' | 'deno' | 'npm' | 'pnpm' | 'yarn';
|
||||
|
||||
interface PackageManagerHandler {
|
||||
install(path: string, packages: string[]): Promise<void> | void,
|
||||
}
|
||||
|
||||
type Config = {
|
||||
description?: string,
|
||||
manual: true,
|
||||
name: string,
|
||||
options: [{
|
||||
configs?: string[],
|
||||
detect?: undefined,
|
||||
name: 'yes',
|
||||
packages?: { [key: string]: ([string, string] | string)[] | string, },
|
||||
presets?: string[],
|
||||
rules?: string[],
|
||||
}],
|
||||
type: 'confirm',
|
||||
} | {
|
||||
description?: string,
|
||||
manual?: boolean,
|
||||
name: string,
|
||||
options: {
|
||||
configs?: string[],
|
||||
detect?: string[] | true,
|
||||
name: string,
|
||||
packages?: { [key: string]: ([string, string] | string)[] | string, },
|
||||
presets?: string[],
|
||||
rules?: string[],
|
||||
}[],
|
||||
type: 'multiple' | 'single',
|
||||
};
|
||||
|
||||
type CliArgs = {
|
||||
configs: Config[],
|
||||
dir: string,
|
||||
installPkgs?: PackageManagerName | boolean,
|
||||
mergeToRoot?: boolean,
|
||||
packages?: string[],
|
||||
} & OptionValues;
|
||||
|
||||
interface ConfigFile {
|
||||
configs: string[],
|
||||
content?: string,
|
||||
imports: Map<string, ([string, string] | string)[] | string>,
|
||||
path: string,
|
||||
presets: string[],
|
||||
rules: string[],
|
||||
}
|
||||
|
||||
interface Package {
|
||||
config?: Map<string, string[]>,
|
||||
configFile?: ConfigFile,
|
||||
directories: string[],
|
||||
files: string[],
|
||||
name: string,
|
||||
path: string,
|
||||
root?: boolean,
|
||||
}
|
||||
|
||||
|
||||
export type {
|
||||
CliArgs,
|
||||
Config,
|
||||
ConfigFile,
|
||||
Package,
|
||||
PackageManagerHandler,
|
||||
PackageManagerName,
|
||||
};
|
||||
269
packages/cli/src/workspace.js
Normal file
269
packages/cli/src/workspace.js
Normal file
@@ -0,0 +1,269 @@
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
import path, { join } from 'node:path';
|
||||
import { existsSync } from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
import picomatch from 'picomatch';
|
||||
import YAML from 'yaml';
|
||||
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Promise<T>} promise - The async function to try running.
|
||||
* @returns {Promise<T | null>} - Returns the result of the async function, or null if it errors.
|
||||
*/
|
||||
async function tryRun(promise) {
|
||||
try {
|
||||
return await promise;
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory - The directory to find .gitignore and .eslintignore.
|
||||
* @returns {Promise<string[]>} - List of ignore glob patterns.
|
||||
*/
|
||||
async function getIgnoredFiles(directory) {
|
||||
const gitIgnore = (
|
||||
await tryRun(fs.readFile(join(directory, '.gitignore'), 'utf8')) ??
|
||||
'')
|
||||
.split('\n')
|
||||
.filter(p => p && !p.startsWith('#'))
|
||||
.map(p => join(directory, '**', p));
|
||||
|
||||
const eslintIgnore = (
|
||||
await tryRun(fs.readFile(join(directory, '.eslintignore'), 'utf8')) ??
|
||||
'')
|
||||
.split('\n')
|
||||
.filter(p => p && !p.startsWith('#'))
|
||||
.map(p => join(directory, '**', p));
|
||||
|
||||
return [...eslintIgnore, ...gitIgnore];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory - The directory to work in.
|
||||
* @returns {Promise<string>} - The package name founded.
|
||||
*/
|
||||
async function getPackageName(directory) {
|
||||
if (existsSync(join(directory, 'package.json'))) {
|
||||
const file = await fs.readFile(join(directory, 'package.json'), 'utf8');
|
||||
/** @type {{name?: string}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const object = JSON.parse(file);
|
||||
|
||||
if (object.name) return object.name;
|
||||
}
|
||||
return path.normalize(directory).split('/').at(-1) ?? directory;
|
||||
}
|
||||
|
||||
export default class Workspace {
|
||||
/**
|
||||
* @param {string} directory -
|
||||
* The directory to get the workspace from.
|
||||
* @param {string[] | false} [packagePatterns] -
|
||||
* List of package patterns (`false` to explicitly tell that this workspace is not a monorepo).
|
||||
*/
|
||||
constructor(directory, packagePatterns) {
|
||||
this.dir = directory;
|
||||
this.packagePatterns = packagePatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<string[]>} - List of packages on a directory;.
|
||||
*/
|
||||
async getPackagePatterns() {
|
||||
/** @type {string[]} */
|
||||
const packagePatterns = [];
|
||||
|
||||
const pnpmWorkspace =
|
||||
existsSync(join(this.dir, 'pnpm-workspace.yaml'))
|
||||
? 'pnpm-workspace.yaml'
|
||||
: (existsSync(join(this.dir, 'pnpm-workspace.yml'))
|
||||
? 'pnpm-workspace.yml'
|
||||
: null);
|
||||
|
||||
if (pnpmWorkspace) {
|
||||
const pnpmWorkspaceYaml = await fs.readFile(
|
||||
join(this.dir, pnpmWorkspace),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
/** @type {{packages?: string[]}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const pnpmWorkspaceObject = YAML.parse(pnpmWorkspaceYaml);
|
||||
|
||||
packagePatterns.push(...pnpmWorkspaceObject.packages ?? []);
|
||||
}
|
||||
else if (existsSync(join(this.dir, 'package.json'))) {
|
||||
const packageJson = await fs.readFile(
|
||||
join(this.dir, 'package.json'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
/** @type {{workspaces?: string[]}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const packageJsonObject = JSON.parse(packageJson);
|
||||
|
||||
packagePatterns.push(...packageJsonObject.workspaces ?? []);
|
||||
}
|
||||
|
||||
return packagePatterns.map((p) => {
|
||||
p = path.normalize(p);
|
||||
p = p.startsWith('/') ? p.replace('/', '') : p;
|
||||
p = p.endsWith('/') ? p.slice(0, -1) : p;
|
||||
return p;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<import('./types').Package[]>} -
|
||||
* The list of packages that exist in the workspace.
|
||||
*/
|
||||
async getPackages() {
|
||||
const paths = await this.getPaths();
|
||||
|
||||
/** @type {import('./types').Package} */
|
||||
const rootPackage = {
|
||||
directories: paths.directories,
|
||||
files: paths.files,
|
||||
name: await getPackageName(this.dir),
|
||||
path: this.dir,
|
||||
root: true,
|
||||
};
|
||||
|
||||
if (this.packagePatterns === false) return [rootPackage];
|
||||
|
||||
const packagePatterns =
|
||||
this.packagePatterns ??
|
||||
await this.getPackagePatterns();
|
||||
|
||||
const packagePaths = paths.directories.filter(d =>
|
||||
picomatch.isMatch(d, packagePatterns),
|
||||
);
|
||||
|
||||
/** @type {import('./types').Package[]} */
|
||||
const packages = [];
|
||||
|
||||
for (const packagePath of packagePaths) {
|
||||
packages.push({
|
||||
directories: paths.directories
|
||||
.filter(d => picomatch.isMatch(d, `${packagePath}/**/*`))
|
||||
.map(d => d.replace(`${packagePath}/`, '')),
|
||||
files: paths.files
|
||||
.filter(f => picomatch.isMatch(f, `${packagePath}/**/*`))
|
||||
.map(f => f.replace(`${packagePath}/`, '')),
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
name: await getPackageName(join(this.dir, packagePath)),
|
||||
path: join(this.dir, packagePath),
|
||||
root: false,
|
||||
});
|
||||
|
||||
rootPackage.files = rootPackage.files
|
||||
.filter(f => picomatch.isMatch(f, `!${packagePath}/**/*`));
|
||||
|
||||
rootPackage.directories = rootPackage.directories
|
||||
.filter(d => picomatch.isMatch(d, `!${packagePath}/**/*`));
|
||||
}
|
||||
|
||||
return [rootPackage, ...packages];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [directory] - The directory to work on.
|
||||
* @param {string[]} [ignores] - Glob patterns to ignore.
|
||||
* @returns {Promise<{files: string[], directories: string[]}>} -
|
||||
* List of all files in the directory.
|
||||
*/
|
||||
async getPaths(directory = this.dir, ignores = []) {
|
||||
ignores.push(
|
||||
...[
|
||||
'.git',
|
||||
'.dist',
|
||||
'.DS_Store',
|
||||
'node_modules',
|
||||
].map(f => join(directory, f)),
|
||||
...await getIgnoredFiles(directory),
|
||||
);
|
||||
|
||||
const pathsUnfiltered = await fs.readdir(directory);
|
||||
const paths = pathsUnfiltered
|
||||
.map(f => path.normalize(join(directory, f)))
|
||||
.filter(p => !picomatch.isMatch(p, ignores));
|
||||
|
||||
/** @type {string[]} */
|
||||
const files = [];
|
||||
/** @type {string[]} */
|
||||
const directories = [];
|
||||
|
||||
for (const p of paths) {
|
||||
// eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member
|
||||
if ((await fs.lstat(p)).isDirectory()) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const subPaths = await this.getPaths(p, ignores);
|
||||
directories.push(p, ...subPaths.directories);
|
||||
files.push(...subPaths.files);
|
||||
}
|
||||
else {
|
||||
files.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
directories: directories.map(p =>
|
||||
path.normalize(p.replace(this.dir, './')),
|
||||
),
|
||||
files: files.map(p => path.normalize(p.replace(this.dir, './'))),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./types').Package[]} packages - Packages to be merged into root.
|
||||
* @returns {[import('./types').Package]} A array containing only the root package.
|
||||
*/
|
||||
static mergePackages(packages) {
|
||||
const rootPackage = packages.find(p => p.root) ?? packages[0];
|
||||
|
||||
// TODO [>=1.0.0]: Refactor this to remove the use of Array#reduce()
|
||||
// eslint-disable-next-line unicorn/no-array-reduce
|
||||
const merged = packages.reduce((accumulated, package_) => {
|
||||
const files = [...new Set([
|
||||
...accumulated.files,
|
||||
...package_.files.map(f => join(package_.path, f)),
|
||||
]
|
||||
.map(p => p.replace(`${rootPackage.path}/`, '')),
|
||||
)];
|
||||
|
||||
const directories = [...new Set([
|
||||
...accumulated.directories,
|
||||
...package_.directories.map(d => join(package_.path, d)),
|
||||
]
|
||||
.map(p => p.replace(`${rootPackage.path}/`, ''))),
|
||||
];
|
||||
|
||||
const mergedConfig = new Map();
|
||||
for (const [config, options] of package_.config ?? []) {
|
||||
const accumulatedOptions =
|
||||
accumulated.config?.get(config) ??
|
||||
[];
|
||||
mergedConfig.set(
|
||||
config,
|
||||
[...new Set([...options, ...accumulatedOptions])],
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
config: mergedConfig,
|
||||
directories,
|
||||
files,
|
||||
name: rootPackage.name,
|
||||
path: rootPackage.path,
|
||||
root: true,
|
||||
};
|
||||
}, rootPackage);
|
||||
|
||||
return [merged];
|
||||
}
|
||||
}
|
||||
3
packages/core/index.d.ts
vendored
3
packages/core/index.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
import type { Config, ESConfig } from './src/types';
|
||||
|
||||
export async function defineConfig(config: Config): Promise<ESConfig[]>;
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "@eslit/core",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"module": "./src/index.js",
|
||||
"source": "./src/index.js",
|
||||
"exports": {
|
||||
"default": "./src/index.js",
|
||||
"import": "./src/index.js",
|
||||
"types": "./index.d.ts"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "./src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "ESLINT_USE_FLAT_CONFIG=true eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/eslint__js": "^8.42.0",
|
||||
"@types/node": "^20.4.2",
|
||||
"eslint": "^8.45.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^2.1.0",
|
||||
"@eslint/js": "^8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"eslint-plugin-jsdoc": "^46.4.4",
|
||||
"globals": "^13.20.0",
|
||||
"yaml": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.45.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
/**
|
||||
* @see {@link https://www.npmjs.org/package/eslint-plugin-jsdoc npm package}
|
||||
*
|
||||
* @summary JSDoc specific linting rules for ESLint.
|
||||
*
|
||||
* ---
|
||||
* **Note:** Types in this project where overridden to be compatible with ESLint new flat
|
||||
* config types. ESlint already has backwards compatibility for plugins not created in the
|
||||
* new flat config.
|
||||
*/
|
||||
declare module 'eslint-plugin-jsdoc' {
|
||||
interface jsDocESlintPlugin extends ESLint.Plugin {
|
||||
configs: ESLint.Plugin['configs'] & {
|
||||
recommended: ESLint.ConfigData
|
||||
'recommended-error': ESLint.ConfigData
|
||||
'recommended-typescript': ESLint.ConfigData
|
||||
'recommended-typescript-error': ESLint.ConfigData
|
||||
'recommended-typescript-flavor': ESLint.ConfigData
|
||||
'recommended-typescript-flavor-error': ESLint.ConfigData
|
||||
}
|
||||
}
|
||||
declare const plugin: jsDocESlintPlugin;
|
||||
export default plugin;
|
||||
}
|
||||
10
packages/core/src/@types/globals.d.ts
vendored
10
packages/core/src/@types/globals.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
declare module 'globals' {
|
||||
const globals: {
|
||||
builtin: Record<string, boolean>
|
||||
browser: Record<string, boolean>
|
||||
node: Record<string, boolean>
|
||||
nodeBuiltin: Record<string, boolean>
|
||||
commonjs: Record<string, boolean>
|
||||
};
|
||||
export default globals;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
/**
|
||||
* Common configuration related to language features of Javascript and Typescript
|
||||
*
|
||||
* @type {import('../types').ESConfig}
|
||||
*/
|
||||
const config = {
|
||||
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': ['error', {
|
||||
'ts-ignore': 'allow-with-description',
|
||||
}],
|
||||
'@typescript-eslint/ban-tslint-comment': 'error',
|
||||
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
|
||||
// Extension rules
|
||||
|
||||
'no-dupe-class-members': 'off',
|
||||
'@typescript-eslint/no-dupe-class-members': 'error',
|
||||
|
||||
'no-invalid-this': 'off',
|
||||
'@typescript-eslint/no-invalid-this': 'error',
|
||||
|
||||
'no-redeclare': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'error',
|
||||
|
||||
'no-empty-function': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'error',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,58 +0,0 @@
|
||||
import globals from 'globals';
|
||||
|
||||
/**
|
||||
* @param {import('../types').Config['environment']} environment
|
||||
* Manual configuration of environments, if undefined,
|
||||
* the function tries to detect the environment automatically
|
||||
* @returns {import('../types').ESConfig[]}
|
||||
* ESLint configuration with global variables and environment
|
||||
*/
|
||||
export function environments(environment) {
|
||||
|
||||
environment ||= {
|
||||
node:
|
||||
typeof window === 'undefined' &&
|
||||
typeof process !== 'undefined' &&
|
||||
typeof require !== 'function',
|
||||
deno:
|
||||
typeof window !== 'undefined' &&
|
||||
// @ts-expect-error because this package is develop in node
|
||||
typeof Deno !== 'undefined',
|
||||
browser:
|
||||
typeof window !== 'undefined',
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
|
||||
languageOptions: {
|
||||
ecmaVersion: environment.ecmaVersion ?? 'latest',
|
||||
globals: {
|
||||
...globals.builtin,
|
||||
...environment.customGlobals,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.cjs', '**/*.cts'],
|
||||
languageOptions: {
|
||||
sourceType: 'commonjs',
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.commonjs,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js', '**/*.mjs', '**/*.ts', '**/*.mts'],
|
||||
languageOptions: {
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...(environment.node ? globals.nodeBuiltin : {}),
|
||||
...(environment.browser || environment.deno ? globals.browser : {}),
|
||||
...(environment.deno ? { Deno: true } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Formatting rules/configuration for Javascript and Typescript
|
||||
*
|
||||
* @type {import('../types').ESConfig}
|
||||
*/
|
||||
const config = {
|
||||
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
// Formatting rules
|
||||
|
||||
'brace-style': 'off',
|
||||
'@typescript-eslint/brace-style': ['error', 'stroustrup', { allowSingleLine: true }],
|
||||
|
||||
'comma-dangle': 'off',
|
||||
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
||||
|
||||
'indent': 'off',
|
||||
'@typescript-eslint/indent': ['error', process.env.READABLE_ESLINT_OPTIONS?.indent === 'space' ? 2 : 'tab', {
|
||||
SwitchCase: 1,
|
||||
VariableDeclarator: 1,
|
||||
outerIIFEBody: 1,
|
||||
MemberExpression: 1,
|
||||
FunctionDeclaration: { parameters: 1, body: 1 },
|
||||
FunctionExpression: { parameters: 1, body: 1 },
|
||||
CallExpression: { arguments: 1 },
|
||||
ArrayExpression: 1,
|
||||
ObjectExpression: 1,
|
||||
ImportDeclaration: 1,
|
||||
flatTernaryExpressions: false,
|
||||
offsetTernaryExpressions: true,
|
||||
ignoreComments: false,
|
||||
ignoredNodes: [
|
||||
'TemplateLiteral *',
|
||||
'JSXElement',
|
||||
'JSXElement > *',
|
||||
'JSXAttribute',
|
||||
'JSXIdentifier',
|
||||
'JSXNamespacedName',
|
||||
'JSXMemberExpression',
|
||||
'JSXSpreadAttribute',
|
||||
'JSXExpressionContainer',
|
||||
'JSXOpeningElement',
|
||||
'JSXClosingElement',
|
||||
'JSXFragment',
|
||||
'JSXOpeningFragment',
|
||||
'JSXClosingFragment',
|
||||
'JSXText',
|
||||
'JSXEmptyExpression',
|
||||
'JSXSpreadChild',
|
||||
'TSTypeParameterInstantiation',
|
||||
'FunctionExpression > .params[decorators.length > 0]',
|
||||
'FunctionExpression > .params > :matches(Decorator, :not(:first-child))',
|
||||
'ClassBody.body > PropertyDefinition[decorators.length > 0] > .key',
|
||||
],
|
||||
}],
|
||||
|
||||
'keyword-spacing': 'off',
|
||||
'@typescript-eslint/keyword-spacing': ['error', { before: true, after: true }],
|
||||
|
||||
'lines-between-class-members': 'off',
|
||||
'@typescript-eslint/lines-between-class-members': ['error'],
|
||||
|
||||
'no-extra-parens': 'off',
|
||||
'@typescript-eslint/no-extra-parens': ['error', 'functions'],
|
||||
|
||||
'object-curly-spacing': 'off',
|
||||
'@typescript-eslint/object-curly-spacing': ['error', 'always'],
|
||||
|
||||
'quotes': 'off',
|
||||
'@typescript-eslint/quotes': ['error', process.env.READABLE_ESLINT_OPTIONS?.quotes ?? 'single'],
|
||||
|
||||
'semi': 'off',
|
||||
'@typescript-eslint/semi': ['error', 'always'],
|
||||
|
||||
'space-before-blocks': 'off',
|
||||
'@typescript-eslint/space-before-blocks': ['error', process.env.READABLE_ESLINT_OPTIONS?.semi ?? 'always'],
|
||||
|
||||
'space-before-function-paren': 'off',
|
||||
'@typescript-eslint/space-before-function-paren': ['error', {
|
||||
anonymous: 'always',
|
||||
named: 'never',
|
||||
asyncArrow: 'always',
|
||||
}],
|
||||
|
||||
'space-infix-ops': 'off',
|
||||
'@typescript-eslint/space-infix-ops': 'error',
|
||||
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,5 +0,0 @@
|
||||
export { default as common } from './common.js';
|
||||
export { default as formatting } from './formatting.js';
|
||||
export { default as jsdoc } from './jsdoc.js';
|
||||
export { default as typescript } from './typescript.js';
|
||||
export * from './environments.js';
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* JSDoc rules overrides
|
||||
*
|
||||
* @type {import('../types').ESConfig}
|
||||
*/
|
||||
const config = {
|
||||
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
|
||||
rules: {
|
||||
'jsdoc/tag-lines': ['error', 'always', {
|
||||
count: 1,
|
||||
applyToEndTag: false,
|
||||
startLines: 1,
|
||||
endLines: 0,
|
||||
tags: {
|
||||
param: { lines: 'never' },
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,63 +0,0 @@
|
||||
import jsdoc from 'eslint-plugin-jsdoc';
|
||||
|
||||
/**
|
||||
* Typescript specific configuration
|
||||
*
|
||||
* @type {import('../types').ESConfig}
|
||||
*/
|
||||
const config = {
|
||||
files: ['**/*.ts', '**/*.cts', '**/*.mts'],
|
||||
// See plugins['jsdoc'] on index.js for more info on this error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
rules: {
|
||||
|
||||
// See plugins['jsdoc'] on index.js for more info on this error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
...jsdoc.configs['recommended-typescript-error'].rules,
|
||||
|
||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||
'@typescript-eslint/array-type': 'error',
|
||||
'@typescript-eslint/class-literal-property-style': 'error',
|
||||
'@typescript-eslint/consistent-generic-constructors': 'error',
|
||||
'@typescript-eslint/consistent-indexed-object-style': 'error',
|
||||
'@typescript-eslint/consistent-type-assertions': 'error',
|
||||
'@typescript-eslint/consistent-type-definitions': 'error',
|
||||
'@typescript-eslint/consistent-type-exports': ['error'],
|
||||
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', disallowTypeAnnotations: true }],
|
||||
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
|
||||
'@typescript-eslint/member-delimiter-style': ['error', { multiline: { delimiter: 'none' } }],
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/prefer-for-of': 'error',
|
||||
'@typescript-eslint/prefer-function-type': 'error',
|
||||
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||
|
||||
...(
|
||||
/** @type {() => import('eslint').Linter.RulesRecord} */
|
||||
() => {
|
||||
const inferrableTypes = process.env.READABLE_ESLINT_OPTIONS?.inferrableTypes ?? 'never';
|
||||
|
||||
if (typeof inferrableTypes === 'string') {
|
||||
return {
|
||||
'@typescript-eslint/explicit-function-return-type': inferrableTypes === 'always' ? 'off' : 'error',
|
||||
'@typescript-eslint/no-inferrable-types': inferrableTypes === 'always' ? 'off' : 'error',
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
'@typescript-eslint/explicit-function-return-type': inferrableTypes[1].returnValues ? 'off' : 'error',
|
||||
'@typescript-eslint/no-inferrable-types': [
|
||||
inferrableTypes[0] === 'always' ? 'off' : 'error',
|
||||
{
|
||||
ignoreParameters: inferrableTypes[1].parameters ?? false,
|
||||
ignoreProperties: inferrableTypes[1].properties ?? false,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
)(),
|
||||
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
12
packages/core/src/env.d.ts
vendored
12
packages/core/src/env.d.ts
vendored
@@ -1,12 +0,0 @@
|
||||
import type { Config } from './types';
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
READABLE_ESLINT_STRICT: Config['strict']
|
||||
READABLE_ESLINT_OPTIONS: Config['options']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -1,15 +0,0 @@
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
import javascript from '@eslint/js';
|
||||
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
// mimic CommonJS variables
|
||||
export const __filename = fileURLToPath(import.meta.url);
|
||||
export const __dirname = path.dirname(__filename);
|
||||
|
||||
export const eslintrc = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: javascript.configs.recommended,
|
||||
allConfig: javascript.configs.all,
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
import { eslintrc } from './eslintrc-compact.js';
|
||||
import tsESlint from '@typescript-eslint/eslint-plugin';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import jsdoc from 'eslint-plugin-jsdoc';
|
||||
import js from '@eslint/js';
|
||||
import * as configs from './configs/index.js';
|
||||
import { getTsConfigs } from './tsconfigs.js';
|
||||
|
||||
/**
|
||||
* @param {import('./types').Config} userConfig
|
||||
* User configuration
|
||||
* @returns {Promise<import('./types').ESConfig[]>}
|
||||
* The complete list of configs for ESLint
|
||||
*/
|
||||
export async function defineConfig(userConfig) {
|
||||
|
||||
userConfig.strict ??= true;
|
||||
userConfig.rootDir ??= process.cwd();
|
||||
userConfig.tsconfig ??= await getTsConfigs(userConfig.rootDir);
|
||||
|
||||
process.env.READABLE_ESLINT_STRICT = userConfig.strict;
|
||||
process.env.READABLE_ESLINT_OPTIONS = {
|
||||
inferrableTypes: userConfig.strict ? 'always' : 'never',
|
||||
...userConfig.options,
|
||||
};
|
||||
|
||||
const userOverrides = (typeof userConfig.overrides !== 'function'
|
||||
? userConfig.overrides
|
||||
: await userConfig.overrides(eslintrc)) ?? [];
|
||||
|
||||
return [
|
||||
{
|
||||
ignores: [
|
||||
'**/node_modules',
|
||||
'**/dist',
|
||||
'**/fixtures',
|
||||
'**/pnpm-lock.yaml',
|
||||
'**/yarn.lock',
|
||||
'**/package-lock.json',
|
||||
],
|
||||
},
|
||||
js.configs.recommended,
|
||||
{
|
||||
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
|
||||
plugins: {
|
||||
'@typescript-eslint': tsESlint,
|
||||
/**
|
||||
* @todo
|
||||
* Fix eslint-plugin-jsdoc type definitions.
|
||||
* _Typescript should have detected [eslint-plugin-jsdoc.d.ts](./@types/eslint-plugin-jsdoc.d.ts)._
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
'jsdoc': jsdoc,
|
||||
},
|
||||
languageOptions: {
|
||||
sourceType: 'module',
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
project: userConfig.tsconfig,
|
||||
tsconfigRootDir: userConfig.rootDir,
|
||||
},
|
||||
},
|
||||
// See plugins['jsdoc'] for more info on this error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
rules: {
|
||||
...tsESlint.configs.recommended.rules,
|
||||
...tsESlint.configs['recommended-requiring-type-checking'].rules,
|
||||
...tsESlint.configs['eslint-recommended'].rules,
|
||||
...(userConfig.strict ? tsESlint.configs.strict.rules : null),
|
||||
// See plugins['jsdoc'] for more info on this error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
...jsdoc.configs['recommended-typescript-flavor-error'].rules,
|
||||
},
|
||||
},
|
||||
configs.common,
|
||||
configs.formatting,
|
||||
configs.jsdoc,
|
||||
configs.typescript,
|
||||
...configs.environments(userConfig.environment),
|
||||
...userOverrides,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join, normalize } from 'node:path';
|
||||
|
||||
/** @type {(...path: string[]) => string} */
|
||||
function toPath(...path) {
|
||||
return normalize(join(...path));
|
||||
}
|
||||
|
||||
/** @type {(...path: string[]) => boolean} */
|
||||
function exists(...path) {
|
||||
return existsSync(toPath(...path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory what the root directory to detect an workspace/monorepo configuration file
|
||||
* @returns {Promise<string[]>} list of possible paths of packages' tsconfig.json and jsconfig.json files
|
||||
*/
|
||||
async function getMonorepoConfigs(directory) {
|
||||
|
||||
/** @type {string[]} */
|
||||
const paths = [];
|
||||
|
||||
if (exists(directory, 'pnpm-workspace.yaml') || exists(directory, 'pnpm-workspace.yml')) {
|
||||
|
||||
const YAML = await import('yaml');
|
||||
|
||||
const yamlFilePath = exists(directory, 'pnpm-workspace.yaml')
|
||||
? join(directory, 'pnpm-workspace.yaml')
|
||||
: join(directory, 'pnpm-workspace.yml');
|
||||
|
||||
/** @type {{packages?: string[], [properties: string]: unknown}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const pnpmWorkspaces = YAML.parse(await readFile(yamlFilePath, 'utf-8'));
|
||||
|
||||
const files = pnpmWorkspaces.packages?.map(w => [
|
||||
toPath(directory, w, 'tsconfig.json'),
|
||||
toPath(directory, w, 'jsconfig.json'),
|
||||
]).flat() ?? [];
|
||||
|
||||
paths.push(...files);
|
||||
|
||||
}
|
||||
else if (exists(directory, 'package.json')) {
|
||||
/** @type {{workspaces?: string[], [properties: string]: unknown}} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const packageJson = JSON.parse(await readFile(join(directory, 'package.json'), 'utf-8'));
|
||||
|
||||
const files = packageJson.workspaces?.map(w => [
|
||||
toPath(directory, w, 'tsconfig.json'),
|
||||
toPath(directory, w, 'jsconfig.json'),
|
||||
]).flat() ?? [];
|
||||
|
||||
paths.push(...files);
|
||||
|
||||
}
|
||||
|
||||
return paths;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} directory what the root directory to work on
|
||||
* @returns {Promise<string[]>} list of tsconfig.json and jsconfig.json file paths
|
||||
*/
|
||||
export async function getTsConfigs(directory) {
|
||||
|
||||
const rootTSConfig = exists(directory, 'tsconfig.eslint.json')
|
||||
? toPath(directory, 'tsconfig.eslint.json')
|
||||
: exists(directory, 'tsconfig.json')
|
||||
? toPath(directory, 'tsconfig.json')
|
||||
: undefined;
|
||||
|
||||
const rootJSConfig = exists(directory, 'jsconfig.eslint.json')
|
||||
? toPath(directory, 'jsconfig.eslint.json')
|
||||
: exists(directory, 'jsconfig.json')
|
||||
? toPath(directory, 'jsconfig.json')
|
||||
: undefined;
|
||||
|
||||
const monorepoConfigs = await getMonorepoConfigs(directory);
|
||||
|
||||
const paths = /** @type {string[]} */
|
||||
([rootTSConfig, rootJSConfig, ...monorepoConfigs]).filter(p => p);
|
||||
|
||||
return paths;
|
||||
|
||||
}
|
||||
112
packages/core/src/types.d.ts
vendored
112
packages/core/src/types.d.ts
vendored
@@ -1,112 +0,0 @@
|
||||
import type { FlatCompat } from '@eslint/eslintrc';
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export type ESConfig = Readonly<Linter.FlatConfig>;
|
||||
|
||||
export interface Config {
|
||||
tsconfig?: string | string[] | true
|
||||
strict?: boolean
|
||||
rootDir?: string
|
||||
/**
|
||||
* @summary
|
||||
* Environment and language settings
|
||||
*
|
||||
* If no globals/environments are defined, the configuration tries to detect the
|
||||
* environment using `typeof`. See each option for more explanation
|
||||
*/
|
||||
environment?: {
|
||||
/**
|
||||
* @summary
|
||||
* Enables NodeJS environment globals.
|
||||
*
|
||||
* **Note:** this does not enables CommonJS globals, if you are using
|
||||
* CommonJS, use a file ending in `.cjs` or `.cts`
|
||||
*
|
||||
* @example // Detects if
|
||||
* typeof window === 'undefined' &&
|
||||
* typeof process !== 'undefined' &&
|
||||
* typeof require !== 'undefined'
|
||||
*/
|
||||
node?: boolean
|
||||
/**
|
||||
* @summary
|
||||
* Enables the global `Deno` namespace and browser/web standards globals
|
||||
*
|
||||
* @example // Detects if
|
||||
* typeof window !== 'undefined' &&
|
||||
* typeof Deno !== 'undefined'
|
||||
*/
|
||||
deno?: boolean
|
||||
/**
|
||||
* @summary
|
||||
* Enables browser/web standards globals
|
||||
*
|
||||
* @example // Detects if
|
||||
* typeof window !== 'undefined'
|
||||
*/
|
||||
browser?: boolean
|
||||
/**
|
||||
* @summary
|
||||
* What JavaScript (ECMAScript) that will be evaluated
|
||||
*
|
||||
* **Defaults to `latest`**
|
||||
*/
|
||||
ecmaVersion?: Linter.ParserOptions['ecmaVersion']
|
||||
/**
|
||||
* @summary
|
||||
* User defined globals for edge-cases or if available aren't enough
|
||||
*
|
||||
* **Does not overrides previous enabled ones**
|
||||
*/
|
||||
customGlobals?: Record<string, boolean>
|
||||
}
|
||||
options?: {
|
||||
indent?: 'tab' | 'space'
|
||||
quotes?: 'single' | 'double'
|
||||
semi?: 'never' | 'always'
|
||||
/**
|
||||
* Typescript's type-checking is able to infer types from parameters.
|
||||
* So using an explicit `:` type annotation isn't obligatory.
|
||||
*
|
||||
* But, **by default in strict mode**, type annotations are always mandated to make
|
||||
* the code more readable, explicit and robust to changes.
|
||||
*
|
||||
* See {@link https://typescript-eslint.io/rules/no-inferrable-types typescript-eslint documentation }
|
||||
* for more info.
|
||||
* ---
|
||||
* **Option: `never`** (default)
|
||||
* Types are always explicit in Typescript
|
||||
*
|
||||
* @example ```ts
|
||||
// Typescript
|
||||
const id: number = 10;
|
||||
const name: string = 'foo';
|
||||
```
|
||||
* ---
|
||||
* **Option: `always`**
|
||||
* Types are always inferred in Typescript
|
||||
*
|
||||
* @example ```ts
|
||||
// Typescript
|
||||
const id = 10;
|
||||
const name = 'foo';
|
||||
```
|
||||
*/
|
||||
inferrableTypes?: inferrableTypesOptions
|
||||
}
|
||||
overrides?:
|
||||
| Linter.FlatConfig[]
|
||||
| ((eslintrc: FlatCompat) => Linter.FlatConfig[] | Promise<Linter.FlatConfig[]>)
|
||||
}
|
||||
|
||||
export type inferrableTypesOptions = [
|
||||
'never' | 'always',
|
||||
{
|
||||
/** @see {@link https://typescript-eslint.io/rules/no-inferrable-types#ignoreparameters} */
|
||||
parameters?: boolean
|
||||
/** @see {@link https://typescript-eslint.io/rules/no-inferrable-types#ignoreproperties} */
|
||||
properties?: boolean
|
||||
/** @see {@link https://typescript-eslint.io/rules/explicit-function-return-type} */
|
||||
returnValues?: boolean
|
||||
},
|
||||
] | 'never' | 'always';
|
||||
11
packages/create-eslegant/bin.js
Normal file
11
packages/create-eslegant/bin.js
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
import process from 'node:process';
|
||||
|
||||
import Cli from '@eslegant/cli';
|
||||
|
||||
const cli = new Cli({
|
||||
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||
configs: (await import('./configs.js')).default,
|
||||
dir: process.cwd(),
|
||||
});
|
||||
await cli.run();
|
||||
37
packages/create-eslegant/configs.js
Normal file
37
packages/create-eslegant/configs.js
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
/** @type {import('@eslegant/cli').Config[]} */
|
||||
const cliConfig = [
|
||||
{
|
||||
description: 'The UI frameworks being used in the project',
|
||||
name: 'framework',
|
||||
options: [
|
||||
{
|
||||
configs: ['svelte.recommended'],
|
||||
detect: ['**/*.svelte', 'svelte.config.{js,ts,cjs,cts}'],
|
||||
name: 'svelte',
|
||||
packages: { svelte: 'svelte' },
|
||||
},
|
||||
{
|
||||
configs: ['vue.recommended'],
|
||||
detect: ['nuxt.config.{js,ts,cjs,cts}', '**/*.vue'],
|
||||
name: 'vue',
|
||||
packages: {
|
||||
svelte: ['hello'],
|
||||
vue: ['vue', ['hello', 'world']],
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'multiple',
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
name: 'strict',
|
||||
options: [{
|
||||
configs: ['config.strict'],
|
||||
name: 'yes',
|
||||
packages: { eslint: 'config', svelte: ['test1'] },
|
||||
}],
|
||||
type: 'confirm',
|
||||
},
|
||||
];
|
||||
export default cliConfig;
|
||||
5
packages/create-eslegant/jsconfig.json
Normal file
5
packages/create-eslegant/jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"exclude": ["./node_modules/**", "./dist/**"],
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
}
|
||||
33
packages/create-eslegant/package.json
Normal file
33
packages/create-eslegant/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "create-eslegant",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"email": "contact.guz013@gmail.com",
|
||||
"name": "Gustavo \"Guz\" L. de Mello",
|
||||
"url": "https://guz.one"
|
||||
},
|
||||
"files": [
|
||||
"bin.js",
|
||||
"configs.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@eslegant/cli": "workspace:*"
|
||||
},
|
||||
"homepage": "https://github.com/LoredDev/ESLegant",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"directory": "packages/create-eslegant",
|
||||
"type": "git",
|
||||
"url": "https://github.com/LoredDev/ESLegant"
|
||||
},
|
||||
"bin": "./bin.js",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.3"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user