Skip to content

Commit

Permalink
feat: typescript (jsdoc) checking and definition generation (#169)
Browse files Browse the repository at this point in the history
* chore: add some low hanging fruit types

* chore: small steps

* chore: Simplify check-existence

* fix: All types in the utils directory

* fix(prefer-promises): missing TraceMap type

* fix(hashbang): Add missing types

* fix: Add types for "process-exit-as-throw"

* chore: unifi TraceMap

* feat: Types for lib/rules

* Update tsconfig.json

Co-authored-by: Sebastian Good <[email protected]>

* ci: Add tsc compile on pack and test

* ci: Export types in package.json

* ci: run tests and linting once

* chore: Remove all "{Object}" from "@typedef"

* fix: Remove typecast infavour of union a discriminator check

* chore: Remove more unneed casting

* fix: Better types from JSON.parse

* chore: 1 typedef per doc comment

* fix: dont cast ErrnoException

* feat: better "TraceMap" types

* chore: Add comment for the "ts-expect-error"

---------

Co-authored-by: 唯然 <[email protected]>
  • Loading branch information
scagood and aladdin-add authored Apr 7, 2024
1 parent 5e82d7f commit 6d8ed14
Show file tree
Hide file tree
Showing 133 changed files with 2,801 additions and 959 deletions.
12 changes: 5 additions & 7 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Install Packages
run: npm install
- name: Lint
run: npm run -s lint
- run: npm install
- run: npm run -s lint
- run: npm run -s test:types

test:
name: Test
Expand Down Expand Up @@ -64,7 +63,6 @@ jobs:
- name: Install Packages
run: npm install
- name: Install ESLint ${{ matrix.eslint }}
run: |
npm install --no-save --force eslint@${{ matrix.eslint }}
run: npm install --no-save --force eslint@${{ matrix.eslint }}
- name: Test
run: npm run -s test:ci
run: npm run -s test:tests
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
/test.js
.eslintcache
.vscode
.idea/
.idea/

types/
36 changes: 17 additions & 19 deletions lib/configs/_commons.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
"use strict"

module.exports = {
commonRules: {
"n/no-deprecated-api": "error",
"n/no-extraneous-import": "error",
"n/no-extraneous-require": "error",
"n/no-exports-assign": "error",
"n/no-missing-import": "error",
"n/no-missing-require": "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/hashbang": "error",
},
}
module.exports.commonRules = /** @type {const} */ ({
"n/no-deprecated-api": "error",
"n/no-extraneous-import": "error",
"n/no-extraneous-require": "error",
"n/no-exports-assign": "error",
"n/no-missing-import": "error",
"n/no-missing-require": "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/hashbang": "error",
})
14 changes: 11 additions & 3 deletions lib/configs/recommended-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
const globals = require("globals")
const { commonRules } = require("./_commons")

// eslintrc config: https://eslint.org/docs/latest/use/configure/configuration-files
/**
* https://eslint.org/docs/latest/use/configure/configuration-files
* @type {import('eslint').ESLint.ConfigData}
*/
module.exports.eslintrc = {
env: {
node: true,
Expand All @@ -30,7 +33,10 @@ module.exports.eslintrc = {
},
}

// flat config: https://eslint.org/docs/latest/use/configure/configuration-files-new
/**
* https://eslint.org/docs/latest/use/configure/configuration-files-new
* @type {import('eslint').Linter.FlatConfig}
*/
module.exports.flat = {
languageOptions: {
sourceType: "module",
Expand All @@ -39,5 +45,7 @@ module.exports.flat = {
...module.exports.eslintrc.globals,
},
},
rules: module.exports.eslintrc.rules,
rules:
/** @type {import('eslint').Linter.RulesRecord} */
(module.exports.eslintrc.rules),
}
14 changes: 11 additions & 3 deletions lib/configs/recommended-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
const globals = require("globals")
const { commonRules } = require("./_commons")

// eslintrc config: https://eslint.org/docs/latest/use/configure/configuration-files
/**
* https://eslint.org/docs/latest/use/configure/configuration-files
* @type {import('eslint').ESLint.ConfigData}
*/
module.exports.eslintrc = {
env: {
node: true,
Expand All @@ -27,7 +30,10 @@ module.exports.eslintrc = {
},
}

// https://eslint.org/docs/latest/use/configure/configuration-files-new
/**
* https://eslint.org/docs/latest/use/configure/configuration-files-new
* @type {import('eslint').Linter.FlatConfig}
*/
module.exports.flat = {
languageOptions: {
sourceType: "commonjs",
Expand All @@ -36,5 +42,7 @@ module.exports.flat = {
...module.exports.eslintrc.globals,
},
},
rules: module.exports.eslintrc.rules,
rules:
/** @type {import('eslint').Linter.RulesRecord} */
(module.exports.eslintrc.rules),
}
13 changes: 11 additions & 2 deletions lib/configs/recommended.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
"use strict"

const getPackageJson = require("../util/get-package-json")
const { getPackageJson } = require("../util/get-package-json")
const moduleConfig = require("./recommended-module")
const scriptConfig = require("./recommended-script")

const packageJson = getPackageJson()
const isModule = (packageJson && packageJson.type) === "module"

const isModule =
packageJson != null &&
typeof packageJson === "object" &&
"type" in packageJson &&
packageJson.type === "module"
const recommendedConfig = isModule ? moduleConfig : scriptConfig

/**
* https://eslint.org/docs/latest/use/configure/configuration-files
* @type {import('eslint').ESLint.ConfigData}
*/
module.exports.eslintrc = {
...recommendedConfig.eslintrc,
overrides: [
Expand Down
80 changes: 80 additions & 0 deletions lib/eslint-utils.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
declare module "eslint-plugin-es-x" {
export const rules: NonNullable<import('eslint').ESLint.Plugin["rules"]>;
}

declare module "@eslint-community/eslint-utils" {
import * as estree from 'estree';
import * as eslint from 'eslint';

type Node = estree.Node | estree.Expression;

export const READ: unique symbol;
export const CALL: unique symbol;
export const CONSTRUCT: unique symbol;
export const ESM: unique symbol;
export class ReferenceTracker {
constructor(globalScope: eslint.Scope.Scope, { mode, globalObjectNames, }?: {
mode?: "legacy" | "strict" | undefined;
globalObjectNames?: string[] | undefined;
} | undefined);
variableStack: eslint.Scope.Variable[];
globalScope: eslint.Scope.Scope;
mode: "legacy" | "strict";
globalObjectNames: string[];
iterateGlobalReferences<Info extends unknown>(traceMap: TraceMap<Info>): IterableIterator<Reference<Info>>;
iterateCjsReferences<Info extends unknown>(traceMap: TraceMap<Info>): IterableIterator<Reference<Info>>;
iterateEsmReferences<Info extends unknown>(traceMap: TraceMap<Info>): IterableIterator<Reference<Info>>;
}
export namespace ReferenceTracker {
export { READ };
export { CALL };
export { CONSTRUCT };
export { ESM };
}
type ReferenceType = typeof READ | typeof CALL | typeof CONSTRUCT;
type TraceMap<Info extends unknown> = {
[READ]?: Info;
[CALL]?: Info;
[CONSTRUCT]?: Info;
[key: string]: TraceMap<Info>;
}
type RichNode = eslint.Rule.Node | Node;
type Reference<Info extends unknown> = {
node: RichNode;
path: string[];
type: ReferenceType;
info: Info;
};

export function findVariable(initialScope: eslint.Scope.Scope, nameOrNode: string | Node): eslint.Scope.Variable | null;

export function getFunctionHeadLocation(node: Extract<eslint.Rule.Node, {
type: 'FunctionDeclaration' | 'FunctionExpression' | 'ArrowFunctionExpression';
}>, sourceCode: eslint.SourceCode): eslint.AST.SourceLocation | null;

export function getFunctionNameWithKind(node: Extract<eslint.Rule.Node, {
type: 'FunctionDeclaration' | 'FunctionExpression' | 'ArrowFunctionExpression';
}>, sourceCode?: eslint.SourceCode | undefined): string;

export function getInnermostScope(initialScope: eslint.Scope.Scope, node: Node): eslint.Scope.Scope;

export function getPropertyName(node: Extract<Node, {
type: 'MemberExpression' | 'Property' | 'MethodDefinition' | 'PropertyDefinition';
}>, initialScope?: eslint.Scope.Scope | undefined): string | null;

export function getStaticValue(node: Node, initialScope?: eslint.Scope.Scope | null | undefined): {
value: unknown;
optional?: never;
} | {
value: undefined;
optional?: true;
} | null;

export function getStringIfConstant(node: Node, initialScope?: eslint.Scope.Scope | null | undefined): string | null;

export function hasSideEffect(node: eslint.Rule.Node, sourceCode: eslint.SourceCode, { considerGetters, considerImplicitTypeConversion }?: VisitOptions | undefined): boolean;
type VisitOptions = {
considerGetters?: boolean | undefined;
considerImplicitTypeConversion?: boolean | undefined;
};
}
138 changes: 77 additions & 61 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,89 @@ const esmConfig = require("./configs/recommended-module")
const cjsConfig = require("./configs/recommended-script")
const recommendedConfig = require("./configs/recommended")

const rules = {
"callback-return": require("./rules/callback-return"),
"exports-style": require("./rules/exports-style"),
"file-extension-in-import": require("./rules/file-extension-in-import"),
"global-require": require("./rules/global-require"),
"handle-callback-err": require("./rules/handle-callback-err"),
"no-callback-literal": require("./rules/no-callback-literal"),
"no-deprecated-api": require("./rules/no-deprecated-api"),
"no-exports-assign": require("./rules/no-exports-assign"),
"no-extraneous-import": require("./rules/no-extraneous-import"),
"no-extraneous-require": require("./rules/no-extraneous-require"),
"no-missing-import": require("./rules/no-missing-import"),
"no-missing-require": require("./rules/no-missing-require"),
"no-mixed-requires": require("./rules/no-mixed-requires"),
"no-new-require": require("./rules/no-new-require"),
"no-path-concat": require("./rules/no-path-concat"),
"no-process-env": require("./rules/no-process-env"),
"no-process-exit": require("./rules/no-process-exit"),
"no-restricted-import": require("./rules/no-restricted-import"),
"no-restricted-require": require("./rules/no-restricted-require"),
"no-sync": require("./rules/no-sync"),
"no-unpublished-bin": require("./rules/no-unpublished-bin"),
"no-unpublished-import": require("./rules/no-unpublished-import"),
"no-unpublished-require": require("./rules/no-unpublished-require"),
"no-unsupported-features/es-builtins": require("./rules/no-unsupported-features/es-builtins"),
"no-unsupported-features/es-syntax": require("./rules/no-unsupported-features/es-syntax"),
"no-unsupported-features/node-builtins": require("./rules/no-unsupported-features/node-builtins"),
"prefer-global/buffer": require("./rules/prefer-global/buffer"),
"prefer-global/console": require("./rules/prefer-global/console"),
"prefer-global/process": require("./rules/prefer-global/process"),
"prefer-global/text-decoder": require("./rules/prefer-global/text-decoder"),
"prefer-global/text-encoder": require("./rules/prefer-global/text-encoder"),
"prefer-global/url-search-params": require("./rules/prefer-global/url-search-params"),
"prefer-global/url": require("./rules/prefer-global/url"),
"prefer-node-protocol": require("./rules/prefer-node-protocol"),
"prefer-promises/dns": require("./rules/prefer-promises/dns"),
"prefer-promises/fs": require("./rules/prefer-promises/fs"),
"process-exit-as-throw": require("./rules/process-exit-as-throw"),
hashbang: require("./rules/hashbang"),
/**
* @typedef {{
'recommended-module': import('eslint').ESLint.ConfigData;
'recommended-script': import('eslint').ESLint.ConfigData;
'recommended': import('eslint').ESLint.ConfigData;
'flat/recommended-module': import('eslint').Linter.FlatConfig;
'flat/recommended-script': import('eslint').Linter.FlatConfig;
'flat/recommended': import('eslint').Linter.FlatConfig;
'flat/mixed-esm-and-cjs': import('eslint').Linter.FlatConfig[];
}} Configs
*/

// Deprecated rules.
"no-hide-core-modules": require("./rules/no-hide-core-modules"),
shebang: require("./rules/shebang"),
}

const mod = {
/** @type {import('eslint').ESLint.Plugin & { configs: Configs }} */
const plugin = {
meta: {
name: pkg.name,
version: pkg.version,
},
rules,
rules: /** @type {Record<string, import('eslint').Rule.RuleModule>} */ ({
"callback-return": require("./rules/callback-return"),
"exports-style": require("./rules/exports-style"),
"file-extension-in-import": require("./rules/file-extension-in-import"),
"global-require": require("./rules/global-require"),
"handle-callback-err": require("./rules/handle-callback-err"),
"no-callback-literal": require("./rules/no-callback-literal"),
"no-deprecated-api": require("./rules/no-deprecated-api"),
"no-exports-assign": require("./rules/no-exports-assign"),
"no-extraneous-import": require("./rules/no-extraneous-import"),
"no-extraneous-require": require("./rules/no-extraneous-require"),
"no-missing-import": require("./rules/no-missing-import"),
"no-missing-require": require("./rules/no-missing-require"),
"no-mixed-requires": require("./rules/no-mixed-requires"),
"no-new-require": require("./rules/no-new-require"),
"no-path-concat": require("./rules/no-path-concat"),
"no-process-env": require("./rules/no-process-env"),
"no-process-exit": require("./rules/no-process-exit"),
"no-restricted-import": require("./rules/no-restricted-import"),
"no-restricted-require": require("./rules/no-restricted-require"),
"no-sync": require("./rules/no-sync"),
"no-unpublished-bin": require("./rules/no-unpublished-bin"),
"no-unpublished-import": require("./rules/no-unpublished-import"),
"no-unpublished-require": require("./rules/no-unpublished-require"),
"no-unsupported-features/es-builtins": require("./rules/no-unsupported-features/es-builtins"),
"no-unsupported-features/es-syntax": require("./rules/no-unsupported-features/es-syntax"),
"no-unsupported-features/node-builtins": require("./rules/no-unsupported-features/node-builtins"),
"prefer-global/buffer": require("./rules/prefer-global/buffer"),
"prefer-global/console": require("./rules/prefer-global/console"),
"prefer-global/process": require("./rules/prefer-global/process"),
"prefer-global/text-decoder": require("./rules/prefer-global/text-decoder"),
"prefer-global/text-encoder": require("./rules/prefer-global/text-encoder"),
"prefer-global/url-search-params": require("./rules/prefer-global/url-search-params"),
"prefer-global/url": require("./rules/prefer-global/url"),
"prefer-node-protocol": require("./rules/prefer-node-protocol"),
"prefer-promises/dns": require("./rules/prefer-promises/dns"),
"prefer-promises/fs": require("./rules/prefer-promises/fs"),
"process-exit-as-throw": require("./rules/process-exit-as-throw"),
hashbang: require("./rules/hashbang"),

// Deprecated rules.
"no-hide-core-modules": require("./rules/no-hide-core-modules"),
shebang: require("./rules/shebang"),
}),
configs: {
"recommended-module": { plugins: ["n"], ...esmConfig.eslintrc },
"recommended-script": { plugins: ["n"], ...cjsConfig.eslintrc },
recommended: { plugins: ["n"], ...recommendedConfig.eslintrc },
"flat/recommended-module": { ...esmConfig.flat },
"flat/recommended-script": { ...cjsConfig.flat },
"flat/recommended": { ...recommendedConfig.flat },
"flat/mixed-esm-and-cjs": [
{ files: ["**/*.js"], ...recommendedConfig.flat },
{ files: ["**/*.mjs"], ...esmConfig.flat },
{ files: ["**/*.cjs"], ...cjsConfig.flat },
],
},
}

// set configs, e.g. mod.configs["recommended-module"]
// do not defined in the mod obj - to avoid circular dependency
mod.configs = {
"recommended-module": { plugins: ["n"], ...esmConfig.eslintrc },
"recommended-script": { plugins: ["n"], ...cjsConfig.eslintrc },
recommended: { plugins: ["n"], ...recommendedConfig.eslintrc },
"flat/recommended-module": { plugins: { n: mod }, ...esmConfig.flat },
"flat/recommended-script": { plugins: { n: mod }, ...cjsConfig.flat },
"flat/recommended": { plugins: { n: mod }, ...recommendedConfig.flat },
"flat/mixed-esm-and-cjs": [
{ plugins: { n: mod }, files: ["**/*.js"], ...recommendedConfig.flat },
{ plugins: { n: mod }, files: ["**/*.mjs"], ...esmConfig.flat },
{ plugins: { n: mod }, files: ["**/*.cjs"], ...cjsConfig.flat },
],
plugin.configs["flat/recommended-module"].plugins = { n: plugin }
plugin.configs["flat/recommended-script"].plugins = { n: plugin }
plugin.configs["flat/recommended"].plugins = { n: plugin }

for (const config of plugin.configs["flat/mixed-esm-and-cjs"]) {
config.plugins = { n: plugin }
}

module.exports = mod
module.exports = plugin
Loading

0 comments on commit 6d8ed14

Please sign in to comment.