nerdamer-prime
Version:
javascript light-weight symbolic math library
752 lines (684 loc) • 26.7 kB
text/typescript
import js from '@eslint/js';
import type { Linter } from 'eslint';
import prettier from 'eslint-config-prettier';
import jsdoc from 'eslint-plugin-jsdoc';
import prettierPlugin from 'eslint-plugin-prettier';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import tseslint from 'typescript-eslint';
const currentDir = dirname(fileURLToPath(import.meta.url));
// =============================================================================
// Shared Constants
// =============================================================================
/** Nerdamer internal types used in JSDoc annotations. These are class names defined within the nerdamer codebase. */
const NERDAMER_TYPES = [
'NerdamerSymbol',
'Frac',
'Vector',
'Matrix',
'Parser',
'Collection',
'Polynomial',
'Factors',
'MVPolynomial',
'Equation',
'Expression',
'AlgebraL',
'Factor',
'Token',
'Tokens',
'Parser.Collection',
];
/**
* Common type aliases found in JSDoc that should be allowed. NOTE: Prefer standard types (number, boolean, string) in
* new code.
*/
const ALLOWED_TYPE_ALIASES = [
// Exception types
'Exception',
'Error',
// TypeScript/JSDoc type keywords
'any',
'void',
'null',
'undefined',
'never',
'bigint',
// Nerdamer-specific result types
'error',
'divergent',
];
/** Legacy nerdamer core source files. These files have relaxed linting rules for progressive adoption. */
const LEGACY_CORE_FILES = [
'nerdamer.core.js',
'Algebra.js',
'Calculus.js',
'Solve.js',
'Extra.js',
'constants.js',
'all.js',
];
// =============================================================================
// JSDoc Configuration
// =============================================================================
/** JSDoc rules configuration. Set to 'warn' for progressive adoption; noisy rules are disabled. */
const jsdocRules: Linter.RulesRecord = {
// Disable overly noisy rules
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-param': 'off',
'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }],
'jsdoc/require-returns-check': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns-type': 'off',
'jsdoc/no-multi-asterisks': 'error',
'jsdoc/reject-function-type': 'off',
'jsdoc/reject-any-type': 'off',
// Rules that catch real issues
'jsdoc/check-param-names': 'error',
'jsdoc/check-tag-names': 'error',
'jsdoc/valid-types': 'error',
// Type checking - enable defaults now that we've renamed Symbol to NerdamerSymbol
// This will flag String->string, Number->number, Boolean->boolean conversions
'jsdoc/check-types': [
'error',
{
noDefaults: false,
unifyParentAndChildTypeChecks: true,
},
],
// Allow nerdamer-specific and common type aliases
'jsdoc/no-undefined-types': [
'error',
{
definedTypes: [...NERDAMER_TYPES, ...ALLOWED_TYPE_ALIASES],
},
],
};
// =============================================================================
// Base JavaScript Rules
// =============================================================================
/** Core JavaScript rules applied to all files. */
const baseJsRules: Linter.RulesRecord = {
// Prettier integration
'prettier/prettier': 'error',
// Console and debugging
'no-console': ['warn', { allow: ['warn', 'error', 'info'] }],
'no-debugger': 'error',
// Variables
'no-unused-vars': [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
caughtErrors: 'none',
},
],
'prefer-const': 'error',
'no-var': 'error',
'no-shadow': 'error',
'no-use-before-define': [
'error',
{
functions: false,
classes: true,
variables: true,
},
],
// Modern syntax preferences
'object-shorthand': ['error', 'always'],
'prefer-template': 'error',
'prefer-arrow-callback': [
'error',
{
allowNamedFunctions: true,
allowUnboundThis: false,
},
],
'arrow-body-style': ['error', 'as-needed'],
'no-duplicate-imports': 'error',
// Best practices
'accessor-pairs': 'error',
'block-scoped-var': 'error',
camelcase: ['error', { properties: 'never', ignoreDestructuring: true, allow: ['^_'] }],
'class-methods-use-this': ['error', { exceptMethods: [] }],
'consistent-return': 'error',
'default-param-last': 'error',
eqeqeq: ['error', 'always', { null: 'ignore' }],
curly: ['error', 'all'],
'default-case-last': 'error',
'dot-notation': 'error',
'func-name-matching': 'error',
'func-names': ['error', 'as-needed'],
'grouped-accessor-pairs': ['error', 'getBeforeSet'],
'guard-for-in': 'error',
'logical-assignment-operators': ['error', 'always', { enforceForIfStatements: true }],
'max-depth': ['error', { max: 6 }],
'max-nested-callbacks': ['error', { max: 5 }],
'max-params': ['error', { max: 6 }],
'new-cap': ['error', { newIsCap: true, capIsNew: false, newIsCapExceptions: ['bigInt', 'bigDec'] }],
'no-alert': 'error',
'no-array-constructor': 'error',
'no-bitwise': ['error', { allow: ['~', '<<', '>>', '>>>'] }],
'no-caller': 'error',
'no-constructor-return': 'error',
'no-div-regex': 'error',
'no-else-return': ['error', { allowElseIf: false }],
'no-empty-function': ['error', { allow: ['arrowFunctions'] }],
'no-eq-null': 'off', // We use eqeqeq with null: 'ignore' instead
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-extra-label': 'error',
'no-implicit-coercion': ['error', { allow: ['!!'] }],
'no-implicit-globals': 'error',
'no-implied-eval': 'error',
'no-invalid-this': 'error',
'no-iterator': 'error',
'no-label-var': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-lonely-if': 'error',
'no-loop-func': 'error',
'no-multi-assign': 'error',
'no-multi-str': 'error',
'no-negated-condition': 'error',
'no-nested-ternary': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-object-constructor': 'error',
'no-octal-escape': 'error',
'no-param-reassign': ['error', { props: false }],
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-proto': 'error',
'no-return-assign': ['error', 'except-parens'],
'no-return-await': 'error',
'no-script-url': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-template-curly-in-string': 'error',
'no-throw-literal': 'error',
'no-undef-init': 'error',
'no-unmodified-loop-condition': 'error',
'no-unneeded-ternary': 'error',
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
'no-useless-call': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
'no-useless-constructor': 'error',
'no-useless-rename': 'error',
'no-useless-return': 'error',
'no-void': 'error',
'one-var': ['error', 'never'],
'operator-assignment': ['error', 'always'],
'prefer-destructuring': [
'error',
{
VariableDeclarator: { array: false, object: true },
AssignmentExpression: { array: false, object: false },
},
],
'prefer-exponentiation-operator': 'error',
'prefer-named-capture-group': 'warn',
'prefer-numeric-literals': 'error',
'prefer-object-has-own': 'error',
'prefer-object-spread': 'error',
'prefer-promise-reject-errors': 'error',
'prefer-regex-literals': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
radix: 'error',
'require-atomic-updates': 'error',
'require-await': 'error',
'require-unicode-regexp': 'warn',
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: true }],
'sort-vars': ['error', { ignoreCase: true }],
'spaced-comment': ['error', 'always', { markers: ['/'], exceptions: ['-', '+', '*'] }],
strict: ['error', 'never'],
'symbol-description': 'error',
'unicode-bom': ['error', 'never'],
'vars-on-top': 'error',
yoda: ['error', 'never'],
// Complexity limits (warnings to track but not block)
complexity: ['warn', { max: 30 }],
'max-classes-per-file': ['warn', { max: 12 }],
'max-lines': ['warn', { max: 2500, skipBlankLines: true, skipComments: true }],
'max-lines-per-function': ['warn', { max: 200, skipBlankLines: true, skipComments: true }],
'max-statements': ['warn', { max: 50 }],
// Code quality warnings
'capitalized-comments': [
'warn',
'always',
{
ignoreConsecutiveComments: true,
ignoreInlineComments: true,
ignorePattern: 'pragma|ignore|prettier-ignore|eslint|webpack|istanbul|c8|v8|tsc',
},
],
'id-denylist': ['warn', 'e', 'err', 'cb', 'callback', 'data', 'temp', 'tmp'],
'id-length': ['warn', { min: 2, exceptions: ['i', 'j', 'k', 'n', 'm', 'x', 'y', 'z', 'a', 'b', 'c', 'd', '_'] }],
'no-await-in-loop': 'warn',
'no-inline-comments': 'off', // Allow inline comments
'no-magic-numbers': [
'warn',
{
ignore: [-1, 0, 1, 2, 10, 100],
ignoreArrayIndexes: true,
ignoreDefaultValues: true,
enforceConst: true,
},
],
'no-ternary': 'off', // Ternaries are fine when used appropriately
'no-underscore-dangle': ['warn', { allowAfterThis: true, allowFunctionParams: true }],
'no-undefined': 'off', // Allow undefined - it's safer in modern JS
'no-warning-comments': ['warn', { terms: ['fixme', 'xxx', 'hack'], location: 'start' }],
'sort-keys': 'off', // Too restrictive for most codebases
// Potential errors (from eslint:recommended but explicitly configured)
'array-callback-return': ['error', { allowImplicit: true, checkForEach: true }],
'no-async-promise-executor': 'error',
'no-class-assign': 'error',
'no-compare-neg-zero': 'error',
'no-cond-assign': ['error', 'except-parens'],
'no-const-assign': 'error',
'no-constant-binary-expression': 'error',
'no-constant-condition': ['error', { checkLoops: false }],
'no-control-regex': 'error',
'no-dupe-args': 'error',
'no-dupe-else-if': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty': ['error', { allowEmptyCatch: true }],
'no-empty-character-class': 'error',
'no-empty-pattern': 'error',
'no-ex-assign': 'error',
'no-fallthrough': ['error', { allowEmptyCase: true }],
'no-func-assign': 'error',
'no-import-assign': 'error',
'no-inner-declarations': 'error',
'no-invalid-regexp': 'error',
'no-irregular-whitespace': 'error',
'no-loss-of-precision': 'error',
'no-misleading-character-class': 'error',
'no-new-native-nonconstructor': 'error',
'no-obj-calls': 'error',
'no-promise-executor-return': ['error', { allowVoid: true }],
'no-prototype-builtins': 'error',
'no-regex-spaces': 'error',
'no-setter-return': 'error',
'no-sparse-arrays': 'error',
'no-unexpected-multiline': 'error',
'no-unreachable': 'error',
'no-unreachable-loop': 'error',
'no-unsafe-finally': 'error',
'no-unsafe-negation': ['error', { enforceForOrderingRelations: true }],
'no-unsafe-optional-chaining': ['error', { disallowArithmeticOperators: true }],
'no-unused-private-class-members': 'error',
'no-useless-backreference': 'error',
'use-isnan': ['error', { enforceForSwitchCase: true, enforceForIndexOf: true }],
'valid-typeof': ['error', { requireStringLiterals: true }],
// Additional potential error rules
'for-direction': 'error',
'getter-return': 'error',
};
// =============================================================================
// TypeScript Rules (Overrides for presets)
// =============================================================================
/**
* TypeScript rule overrides. We use tseslint.configs.strictTypeChecked and stylisticTypeChecked as bases, then apply
* these customizations.
*/
const typescriptRuleOverrides: Linter.RulesRecord = {
// Disable base ESLint rules that conflict with TypeScript equivalents
'no-unused-vars': 'off',
'no-shadow': 'off',
'no-use-before-define': 'off',
'no-redeclare': 'off',
'no-dupe-class-members': 'off',
'no-return-await': 'off',
// Custom configurations for rules from presets
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
caughtErrors: 'none',
},
],
'@typescript-eslint/no-use-before-define': [
'error',
{
functions: false,
classes: true,
variables: true,
typedefs: true,
},
],
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': false,
'ts-nocheck': false,
'ts-check': false,
},
],
'@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
'@typescript-eslint/strict-boolean-expressions': [
'error',
{
allowString: true,
allowNumber: true,
allowNullableObject: true,
},
],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
disallowTypeAnnotations: true,
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/no-inferrable-types': [
'error',
{
ignoreParameters: true,
ignoreProperties: true,
},
],
// Additional rules not in presets
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowExpressions: true,
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
allowDirectConstAssertionInArrowFunctions: true,
allowConciseArrowFunctionExpressionsStartingWithVoid: true,
},
],
'@typescript-eslint/member-ordering': 'error',
'@typescript-eslint/method-signature-style': ['error', 'property'],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase'],
prefix: ['I'],
},
{
selector: 'typeAlias',
format: ['PascalCase'],
},
{
selector: 'enum',
format: ['PascalCase'],
},
],
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/prefer-readonly': 'error',
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/no-shadow': 'error',
// Prevent use of `any` type - enforce proper typing
'@typescript-eslint/no-explicit-any': 'error',
};
// =============================================================================
// Export Configuration
// =============================================================================
export default defineConfig([
// -------------------------------------------------------------------------
// Base Configurations
// -------------------------------------------------------------------------
// ESLint recommended rules
js.configs.recommended,
// JSDoc recommended config
jsdoc.configs['flat/recommended'],
// Prettier integration (must come after other configs to override formatting)
prettier,
// -------------------------------------------------------------------------
// Global Configuration for All Files
// -------------------------------------------------------------------------
{
name: 'global/base',
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
...globals.es2021,
},
},
plugins: {
prettier: prettierPlugin,
jsdoc,
},
rules: {
...jsdocRules,
...baseJsRules,
},
},
// -------------------------------------------------------------------------
// TypeScript Configuration (using preset configs)
// -------------------------------------------------------------------------
...tseslint.configs.strictTypeChecked.map(config => ({
...config,
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
})),
...tseslint.configs.stylisticTypeChecked.map(config => ({
...config,
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
})),
{
name: 'typescript/overrides',
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: currentDir,
},
},
rules: typescriptRuleOverrides,
},
// -------------------------------------------------------------------------
// Test Files Configuration (globals for all test files)
// -------------------------------------------------------------------------
{
name: 'tests/globals',
files: ['**/*.spec.js', '**/*.spec.ts', 'spec/**', 'spec-dts/**'],
languageOptions: {
globals: {
...globals.jest,
...globals.jasmine,
},
},
rules: {
'no-console': 'off',
},
},
// -------------------------------------------------------------------------
// Config Files
// -------------------------------------------------------------------------
{
name: 'config-files',
files: ['*.config.js', '*.config.cjs', '*.config.mjs', '*.config.mts', '.prettierrc.cjs'],
languageOptions: {
globals: {
...globals.node,
},
},
rules: {
'no-console': 'off',
},
},
// -------------------------------------------------------------------------
// Legacy Nerdamer Core Files
// These files have relaxed linting rules because they are mature,
// mathematically complex code that uses patterns not ideal for modern JS
// but work correctly and are too risky to refactor without extensive testing.
// -------------------------------------------------------------------------
{
name: 'legacy/core',
files: LEGACY_CORE_FILES,
rules: {
// Parameter reassignment is common in mathematical algorithms
'no-param-reassign': 'off',
// Short variable names (a, b, x, y, etc.) are conventional in math
'id-length': 'off',
'id-denylist': 'off',
// Mathematical code often uses ++ and -- operators
'no-plusplus': 'off',
// Complex math functions often have many parameters and statements
'max-params': 'off',
'max-statements': 'off',
'max-lines-per-function': 'off',
'max-lines': 'off',
complexity: 'off',
'max-depth': 'off',
// Chained assignment is used for efficiency in numerical code
'no-multi-assign': 'off',
// Magic numbers are common in mathematical constants/algorithms
'no-magic-numbers': 'off',
// Underscore-prefixed variables denote internal/private usage
'no-underscore-dangle': 'off',
},
},
// -------------------------------------------------------------------------
// Legacy Spec Test Files
// -------------------------------------------------------------------------
{
name: 'legacy/spec',
files: ['spec/**/*.js'],
rules: {
// Test files use /* global expect */ but expect is already defined
// via jasmine/jest globals, so we disable the no-redeclare check
'no-redeclare': 'off',
// Test files legitimately use magic numbers for test values
'no-magic-numbers': 'off',
// Test files often use short variable names for brevity
'id-length': 'off',
'id-denylist': 'off',
// Test files can have many statements and lines per function
'max-statements': 'off',
'max-lines-per-function': 'off',
'max-lines': 'off',
},
},
// -------------------------------------------------------------------------
// TypeScript Spec Files (spec-dts)
// These are type specification tests that intentionally test various type
// patterns including unsafe operations and any types.
// -------------------------------------------------------------------------
{
name: 'spec-dts/overrides',
files: ['spec-dts/**/*.ts'],
rules: {
// Disable all TypeScript unsafe/any rules - these tests intentionally
// use any types to verify type definitions work correctly
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
'@typescript-eslint/prefer-for-of': 'off',
// Mathematical code often uses ++ and -- operators
'no-plusplus': 'off',
// Test files often use short variable names for brevity
'id-length': 'off',
// Test files legitimately use magic numbers for test values
'no-magic-numbers': 'off',
// Complex functions often have many parameters and statements
'max-params': 'off',
'max-statements': 'off',
'max-lines-per-function': 'off',
'max-lines': 'off',
complexity: 'off',
'max-depth': 'off',
},
},
// -------------------------------------------------------------------------
// Type Definition File (index.d.ts)
// This is a type definition file for an existing JavaScript library.
// It must reflect the actual runtime API, which includes:
// - Interfaces without 'I' prefix (matching JS class names)
// - Method signatures (matching JS method definitions)
// - Forward references (types are used before defined for readability)
// - Some 'any' types (for truly dynamic JavaScript APIs)
// - Empty interfaces extending base types (semantic type aliases)
// -------------------------------------------------------------------------
{
name: 'type-definitions',
files: ['index.d.ts'],
rules: {
// Interface naming - type definitions must match actual JS class/object names
'@typescript-eslint/naming-convention': 'off',
// Forward references are necessary for readable type definitions
'@typescript-eslint/no-use-before-define': 'off',
// Method signatures are idiomatic in .d.ts files
'@typescript-eslint/method-signature-style': 'off',
// Empty interfaces are used as semantic type aliases
'@typescript-eslint/no-empty-object-type': 'off',
// Some APIs are genuinely dynamic and need 'any'
'@typescript-eslint/no-explicit-any': 'off',
// Function type is needed for some callback APIs
'@typescript-eslint/no-unsafe-function-type': 'off',
// Union types may include redundant constituents for documentation
'@typescript-eslint/no-redundant-type-constituents': 'off',
// Overload signatures are sometimes clearer as separate declarations
'@typescript-eslint/unified-signatures': 'off',
// Member ordering is less important in .d.ts files
'@typescript-eslint/member-ordering': 'off',
// Adjacent overloads rule can conflict with logical grouping
'@typescript-eslint/adjacent-overload-signatures': 'off',
// Identifier restrictions don't apply to documenting external APIs
'id-denylist': 'off',
},
},
// -------------------------------------------------------------------------
// Ignored Files and Directories
// -------------------------------------------------------------------------
{
name: 'ignores',
ignores: [
// Build outputs
'dist/',
'build/',
'coverage/',
'*.min.js',
'*.min.mjs',
'all.min.js',
// Dependencies
'node_modules/',
// Type declarations (generated, in types/ folder)
'types/**/*.d.ts',
// Generated spec output
'spec-dts/dist/',
// Temporary files
'temp/',
'tmp/',
// Git
'.git/',
],
},
]);