114 Commits

Author SHA1 Message Date
renovate[bot]
5707196c66 chore(deps): update dependency @types/eslint__js to v8.42.3 2024-04-10 18:02:36 +00:00
Guz
504deabcb6 Merge pull request #21 from LoredDev/fix-linting-issues
refactor: ♻️ fix eslint errors in @eslegant/cli
2023-09-09 21:46:41 -03:00
Guz013
fa9667ef09 refactor: ♻️ fix eslint errors in @eslegant/cli
This fixes the constant eslint error and warnings in the repository.
It is mostly a temporally solution rather than a actual refactor, so
a lot of eslint-disable comments where used.
2023-09-09 21:40:53 -03:00
Guz013
263e1edb63 feat: browser compatibility checking 2023-09-09 18:30:16 -03:00
Guz013
3aed0f1708 refactor: ♻️ change constants handling 2023-09-09 18:29:28 -03:00
Guz013
4a1f38ff24 feat: new rules inspired by standardjs 2023-09-05 12:22:20 -03:00
Guz013
2e1914c733 feat: security related rules 2023-09-04 20:56:00 -03:00
Guz013
dcce924286 feat: add more NodeJS-specific rules 2023-09-04 18:42:15 -03:00
Guz013
8749e51c13 fix: 🐛 'off' rule level being overridden 2023-09-04 17:26:26 -03:00
Guz013
853b868a24 fix: 🐛 no-extra-parens conflicts 2023-09-04 17:25:24 -03:00
Guz013
f4e52b991c feat: default variant for the configs 2023-09-04 17:12:43 -03:00
Guz
15765c8e73 Merge pull request #18 from LoredDev/13-new-config-files
fix: missing commits
2023-09-04 16:51:05 -03:00
Guz
b41d515e7f chore: merge pull request #17
chore(deps): update actions/checkout action to v4
2023-09-04 16:46:07 -03:00
renovate[bot]
3ccab1d5b2 chore(deps): update actions/checkout action to v4 2023-09-04 19:44:33 +00:00
Guz
47eb42405b chore: merge pull request #16
feat/refactor: new configuration files
2023-09-04 16:44:16 -03:00
Guz013
d8fcf7bfe3 feat: group and sort imports rule 2023-09-04 16:43:14 -03:00
Guz013
71442056ce fix: 🐛 add globals to the core config 2023-09-04 16:41:32 -03:00
Guz013
da3429a868 chore: 🔧 add changeset 2023-09-04 16:38:38 -03:00
Guz013
2ca40e1ef1 fix: 🐛 misc fixes
Fixed problems config not being exported and used in the presets and
some rules not configured properly.
2023-09-02 20:33:13 -03:00
Guz013
3434620ff1 docs: 📚️ add docs to all files
Added documentation to all files, configs and presets. Following the new
strict documentation rules.
2023-09-02 20:31:57 -03:00
Guz013
f874ed622e feat: defineConfig helper function 2023-09-02 20:25:32 -03:00
Guz013
ac7c70e81c feat: rules variations 2023-09-02 08:19:33 -03:00
Guz013
ef6b399bb7 feat: eslint-plugin-perfectionist rules 2023-09-02 06:46:32 -03:00
Guz013
8ec97a8b66 feat: add eslint's max-len rule 2023-09-02 06:42:28 -03:00
Guz013
a2cc881053 feat: jsdoc rules and remove old config 2023-09-02 06:20:45 -03:00
Guz013
10c4155283 fix: 🐛 typescript-eslint missing formatting rules 2023-09-02 05:47:00 -03:00
Guz013
da8d4312a0 feat: eslint-plugin-import rules 2023-09-02 04:41:18 -03:00
Guz013
5dfa998860 feat: typescript-eslint rules 2023-09-02 03:05:33 -03:00
Guz013
8c86b748ad chore: 🔧 add vscode workspaces 2023-09-01 18:22:48 -03:00
Guz013
fc2613f874 chore!: 💥 🔧 rename @eslegant/config to @eslegant/js
Renamed @eslegant/config to @eslegant/js, reflecting the new structure and
purpose of it just having rules related to javascript and
typescript

Moved said package to the new ./configs/ directory. Packages under the
./packages/ directory will be related to ones that aren't related to
ESLint itself and/or its configuration.
2023-09-01 18:08:56 -03:00
Guz013
a64062f3a5 refactor: ♻️ type declarations and exports 2023-09-01 17:40:15 -03:00
Guz013
b6ebbf9eb8 chore: 🔧 commit overrides file (wip config) 2023-09-01 17:28:37 -03:00
Guz013
dd1d0276b0 feat: 🦄 unicorn rules 2023-09-01 17:27:27 -03:00
Guz013
5a6102ff45 fix: 🐛 config merging/extending 2023-09-01 15:10:37 -03:00
Guz013
8f77f4591f docs: 📚️ add small documentation for each config 2023-08-31 17:30:47 -03:00
Guz013
e20c7e6425 feat: strict preset 2023-08-31 17:12:45 -03:00
Guz013
88412b067d feat: "suggestions" and "problems" rules 2023-08-31 17:11:50 -03:00
Guz013
d5e30078d4 feat: new formatting rules 2023-08-31 13:10:21 -03:00
Guz013
b28c7d2c62 refactor!: 💥 ♻️ restructure the config and presets
Now each config is related to a specific language and or purpose,
having different "flavors" or levels of strictness for them

Presets now have the purpose of simply grouping said configs for easier
of them.

BREAKING CHANGE
2023-08-30 15:31:13 -03:00
Guz013
9043156913 chore: 🔧 fix packages names in changesets 2023-08-29 15:52:34 -03:00
Guz013
3f773f5636 chore: 🔧 add changeset 2023-08-29 15:50:25 -03:00
Guz013
aad3b68f65 ci: 👷 update actions with new name 2023-08-29 15:48:22 -03:00
Guz
c3b34735dd chore: merge #15 branch 2023-08-29 15:41:58 -03:00
Guz013
3c23f0b07c chore: 🔧 rename fixtures packages 2023-08-29 15:41:00 -03:00
Guz013
ff44d8b56e chore: 🔧 update links 2023-08-29 15:39:01 -03:00
Guz013
c1aa5d0235 refactor!: 💥 ♻️ rename packages
Renamed all packages and dependencies from "eslit" to "eslegant"

BREAKING CHANGE
2023-08-29 15:31:57 -03:00
Guz
0be127d50a Merge pull request #14 from LoredDev:improve-cli-api
Move configs array to it's own package
2023-08-29 15:14:47 -03:00
Guz013
c061fdc8cd feat!: 💥 export cli api and remove configs array
Removed the configs array from the cli package, as it now is handled by
the eslegant package.
Exported the Cli class so it can be run by other packages

BREAKING CHANGE
2023-08-29 15:08:29 -03:00
Guz013
26f29009d6 feat: create the eslegant package 2023-08-29 15:06:24 -03:00
Guz013
da21030000 feat: add eslint-plugin-i 2023-08-25 17:31:56 -03:00
Guz013
39d323a3ce ci: 👷 add scope to node setup 2023-08-23 12:09:24 -03:00
Guz013
3e866ee562 ci: 👷 fix npm token 2023-08-23 12:03:16 -03:00
Guz013
3bedc6d1ba ci: 👷 fix missing token 2023-08-23 11:55:11 -03:00
Guz013
64a45cd86a style: 🎨 lint project 2023-08-23 11:50:22 -03:00
Guz013
10e543094f chore(deps): 🔗 update dependencies 2023-08-23 11:45:04 -03:00
Guz013
b257ed000f fix(cli): 🐛 small fixes when prompts are canceled 2023-08-23 11:40:16 -03:00
github-actions[bot]
5537a71bf3 ci: 👷 update dev branch 2023-08-23 14:14:41 +00:00
github-actions[bot]
e28cacfbd6 ci: 👷🦋 version packages 2023-08-23 14:13:13 +00:00
Guz013
7d663d7ddf fix(cli): 🐛 publish-config access 2023-08-23 11:10:54 -03:00
Guz
6fda82d3bc Merge pull request #10 from LoredDev/2-eslit-cli-project-setup
feat: Command line interface for setting up eslint/eslit configs
2023-08-23 11:06:16 -03:00
Guz013
64dc504e2a feat(cli): install packages after configuration 2023-08-23 10:54:15 -03:00
Guz013
a830ec71bd refactor: ♻️ completely refactor the ast generation 2023-08-21 13:53:47 -03:00
Guz013
13e517964c fix(deps): 🐛 update lock file 2023-08-14 15:03:22 -03:00
Guz013
4792b485d6 refactor: ♻️ remove unused code 2023-08-14 15:02:47 -03:00
Guz013
568bdb5d97 fix(cli): 🐛 confirm file write operation 2023-08-14 14:58:41 -03:00
Guz013
9a9ffc1a04 feat(cli): write config files 2023-08-14 14:33:44 -03:00
Guz013
5eb7eac8ab feat: generate config imports 2023-08-14 12:04:29 -03:00
Guz013
983d4958f2 feat: write and manipulate eslint.config.js files 2023-08-08 16:08:23 -03:00
Guz013
978f06605e feat: config file object generation 2023-08-07 16:25:04 -03:00
Guz013
1b2891b7ee feat: merge configurations to root config 2023-08-04 16:10:24 -03:00
Guz013
4384f6143a refactor: ♻️ use Map on packages' configs 2023-08-04 15:34:31 -03:00
Guz013
107be3d4ab fix: 🐛 overridden configs in single-package arrays 2023-08-04 14:25:42 -03:00
Guz013
95ad4abf9f feat: "confirm" config type
Added "confirm" config type for yes/no questions/configs
2023-08-04 11:12:32 -03:00
Guz013
d2adda8aeb feat: manual options/configs selection support 2023-08-04 10:07:59 -03:00
Guz013
e775d83ccf refactor: ♻️ repurpose Cli class to Configs
Renamed Cli to ConfigsProcessor, now the Cli class is purposed to just
parsing the cli arguments and orchestrate other classes
2023-08-03 18:33:58 -03:00
Guz013
c201a25e6e feat: pass packages from cli argument 2023-08-03 17:38:03 -03:00
Turbobot
b0e00d6e5c feat(create-turbo): apply official-starter transform 2023-08-03 16:53:26 -03:00
Guz013
73b71033b3 refactor: ♻️ simplify/"normalize" packages detection
Refactored how the cli gets the workspace structure, making it
more "agnostic" for single- and multi- packages workspaces.
Also the paths listed on the package object were simplified to
be relative to the package's path.
2023-08-03 14:19:00 -03:00
Guz013
f24aba4f8e refactor: ♻️ move function 2023-08-03 10:55:56 -03:00
Guz013
8c1e721346 refactor: ♻️ move workspace on its own class 2023-08-01 16:55:49 -03:00
Guz013
c1f4c262dc refactor(cli): ♻️ remove unused debugger 2023-08-01 16:23:14 -03:00
Guz013
f7b6faff09 feat(cli): refactoring and detection of configs 2023-08-01 16:20:17 -03:00
Guz013
48b70de8d9 feat(cli): list directories of packages 2023-08-01 11:19:05 -03:00
Guz013
fdad363313 chore(deps): 🔗 make yaml a dependency and not devdep 2023-08-01 10:57:33 -03:00
Guz013
1296891431 chore: 🔧 add changeset 2023-07-31 19:23:14 -03:00
Guz013
03a9ce3de5 chore(fixtures): 🔧 add package name 2023-07-31 19:20:12 -03:00
Guz013
5752e76197 chore: 🔧 make fixture/library private 2023-07-31 19:19:26 -03:00
Guz013
86c178419c fix(deps): 🐛 commit missing package.json changes 2023-07-31 19:18:30 -03:00
Guz013
41fd41bef6 feat(cli): project structure detection 2023-07-31 19:01:59 -03:00
Guz013
6f1fca2513 chore: 🔧 update changesets 2023-07-21 17:48:15 -03:00
Guz013
48f20e94f7 fix: 🐛 changeset repository 2023-07-21 17:45:18 -03:00
Guz013
e1747ee696 fix(deps): 🐛 add missing dependency for changesets 2023-07-21 17:41:38 -03:00
Guz
5fbc250edd chore: merge pull request #8 from LoredDev/3-eslint-flat-config-by-default
refactor/feat: use a more standard ESLint flat configuration object
2023-07-21 17:38:42 -03:00
Guz013
2f75775f43 chore: 🔧 add changeset 2023-07-21 17:35:19 -03:00
Guz013
9bc8b4fc5b docs: 📚️ add missing documentations 2023-07-21 17:24:55 -03:00
Guz013
c647e6020b fix: 🐛 remove unnecessary tsconfigs.js file 2023-07-21 16:56:20 -03:00
Guz013
53e44d5126 feat: rewrite configuration
Rewrote the configuration and logic of the defineConfig function
to better resemble a standard ESLint configuration.
Also removing the necessity of a function to detect tsconfig/jsconfig
files.

Fixing ESLint flat-config by default #3.
2023-07-21 16:54:29 -03:00
Guz013
a6dc972a5c fix: 🐛 repository directory 2023-07-21 14:30:48 -03:00
Guz013
5aaaa9ac0e chore: 🔧 rename package 2023-07-21 14:29:48 -03:00
Guz013
ac9bfbd232 chore(renovate): 🔧 ignore fixtures packages 2023-07-20 17:52:01 -03:00
Guz013
4533557f4f fix(ci): 🐛 👷 github action path 2023-07-20 17:45:10 -03:00
Guz013
a1e23fa757 fix(ci): 🐛 👷 lock file 2023-07-20 17:44:10 -03:00
Guz013
89f7fc83eb ci: 👷 replace renovate changesets action 2023-07-20 17:43:26 -03:00
Guz
b6dc308639 ci(deps): configure renovate
Configure Renovate
2023-07-20 17:29:47 -03:00
Guz013
e80455e2d4 fix(ci): 🐛 👷 github actor 2023-07-20 17:27:15 -03:00
Guz013
428fdffaf4 chore: 🔧 change to recommended config 2023-07-20 17:24:58 -03:00
Guz013
a474f7277d chore: 🔧 rename workflow 2023-07-20 17:14:53 -03:00
Guz013
f6c47d14f6 ci(renovate): 👷 changesets for renovate 2023-07-20 17:12:55 -03:00
renovate[bot]
3fda9b9156 Add renovate.json 2023-07-20 19:56:42 +00:00
Guz013
2246103ae6 chore: 🔧 change changelog generator 2023-07-20 16:41:55 -03:00
Guz013
2178a36f9b chore: 🔧 update license 2023-07-20 16:29:33 -03:00
Guz013
971bbcaa80 chore: 🔧 placeholder test script/command 2023-07-20 16:12:40 -03:00
Guz013
edb86e3928 chore: 🔧 remove unnecessary env variable 2023-07-20 16:09:36 -03:00
Guz013
855b101292 fix(ci): 🐛 👷 check if branch is main when updating dev 2023-07-20 15:58:18 -03:00
108 changed files with 6050 additions and 1030 deletions

View 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`).

View File

@@ -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": [],

View File

@@ -0,0 +1,5 @@
---
"@eslegant/js": minor
---
Added new ESLint rules inspired by StandardJS.

View 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.

View 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.

View File

@@ -0,0 +1,6 @@
---
"@eslegant/js": patch
"@eslegant/cli": patch
---
Updated dependencies

View 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

View File

@@ -0,0 +1,5 @@
---
"@eslegant/js": minor
---
Configs now export a `default` variation, where rule leves aren't overriden.

View 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.

View 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

View 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`

View File

@@ -0,0 +1,8 @@
---
"create-eslegant": minor
"eslegant": minor
"@eslegant/js": minor
"@eslegant/cli": minor
---
Renamed all packages from "eslit" to "eslegant"

View File

@@ -0,0 +1,5 @@
---
"@eslegant/js": patch
---
Renamed @eslegant/config to @eslegant/js

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View 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");

View File

@@ -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
View 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"
]
}
}

View File

@@ -21,5 +21,13 @@
"json",
"jsonc",
"yaml"
],
"cSpell.words": [
"eslegant",
"estree",
"nanospinner",
"picocolors",
"picomatch",
"sisteransi"
]
}

View File

@@ -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
View 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)).

View 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
View 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
View 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.28.0-2",
"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"
}
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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
View 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;
}

View File

@@ -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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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 };

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
View 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
View 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';

View 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
View 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;

View 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;

View 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;

View 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;

View File

@@ -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'] }],
},
},
]);

View 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

View 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
View 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

View 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/*"
]
}

View File

@@ -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",

View File

@@ -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"
}
}

View 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
View 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
View File

@@ -0,0 +1 @@
export { default } from './src/cli.js';

View File

@@ -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
View 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
View 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();
}
}

View 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');
}
}

View 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;
}
}

View 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;

View 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);
}

View 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;

View 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;
}

View 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
View 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,
};

View 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];
}
}

View File

@@ -1,3 +0,0 @@
import type { Config, ESConfig } from './src/types';
export async function defineConfig(config: Config): Promise<ESConfig[]>;

View File

@@ -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"
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 } : {}),
},
},
},
];
}

View File

@@ -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;

View File

@@ -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';

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {};

View File

@@ -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,
});

View File

@@ -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,
];
}

View File

@@ -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;
}

View File

@@ -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';

View 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();

View 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;

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"exclude": ["./node_modules/**", "./dist/**"],
"include": ["**/*.ts", "**/*.js"],
}

View 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