UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

705 lines (668 loc) 25 kB
"use strict"; import eslint from "@eslint/js"; import tseslint from "@typescript-eslint/eslint-plugin"; import eslintParser from "@typescript-eslint/parser"; import eslintPluginImport from "eslint-plugin-import"; import eslintPluginLodash from "eslint-plugin-lodash"; import eslintPluginNoOnlyTests from "eslint-plugin-no-only-tests"; import eslintPluginNode from "eslint-plugin-n"; import eslintPluginReact from "eslint-plugin-react"; import eslintPluginReactHooks from "eslint-plugin-react-hooks"; import eslintPluginTypescriptEnum from "eslint-plugin-typescript-enum"; import eslintPluginUnusedImports from "eslint-plugin-unused-imports"; import globals from "globals"; import stylistic from "@stylistic/eslint-plugin"; // Custom local rules for Slapdash codebase const localRules = { rules: { "zod-prefer-safe-nullish": { meta: { type: "problem", docs: { description: "Prefer .safeNullish() over .optional(), .nullable(), or .nullish() for LLM response Zod schemas", category: "Best Practices", recommended: true, }, messages: { preferSafeNullish: "Use .safeNullish() instead of .{{method}}() for LLM response schemas. " + ".safeNullish() handles both undefined and null values, and it converts null to undefined " + "to avoid the need for null checks. LLMs sometimes generate null instead of undefined " + "and vice versa, so .safeNullish() is more robust.", }, schema: [], }, create(context) { return { MemberExpression(node) { // Check if this is a method call like .optional(), .nullable(), or .nullish() if ( node.property && node.property.type === "Identifier" && ["optional", "nullable", "nullish"].includes(node.property.name) ) { // Check if this is being called (e.g., .optional() not just .optional) const parent = node.parent; if ( parent && parent.type === "CallExpression" && parent.callee === node ) { // Try to determine if this is a Zod schema // This is a heuristic: we look for z. in the call chain let current = node.object; let isZodSchema = false; // Walk up the chain looking for z.something while (current) { if ( current.type === "MemberExpression" && current.object && current.object.type === "Identifier" && current.object.name === "z" ) { isZodSchema = true; break; } if (current.type === "CallExpression") { current = current.callee; } else if (current.type === "MemberExpression") { current = current.object; } else { break; } } if (isZodSchema) { context.report({ node: node.property, messageId: "preferSafeNullish", data: { method: node.property.name, }, }); } } } }, }; }, }, }, }; // Fixing a bug in the eslint-plugin-react library. const browserGlobals = { ...globals.browser, AudioWorkletGlobalScope: false, // this is the default, }; delete browserGlobals["AudioWorkletGlobalScope "]; delete tseslint.configs.recommended["extends"]; function toFlatConfig(plugin, name) { delete plugin.configs.recommended["extends"]; const parserOptions = plugin.configs.recommended.parserOptions; delete plugin.configs.recommended["parserOptions"]; return { ...plugin.configs.recommended, plugins: { [name]: plugin }, ...(parserOptions ? { languageOptions: { parserOptions } } : {}), }; } export default function createConfig({ projectRoot, eslintTsConfig, extraRules, extraIgnorePatterns, }) { return [ { ignores: [ "**/node_modules/**/*", "dist/**", "**/webpack.config.ts", "**/bin/**", "**/*.d.ts", "**/*.sh", "*.js", "src/scripts/code_templates/*", ...(extraIgnorePatterns ?? []), ], }, eslint.configs.recommended, toFlatConfig(eslintPluginReact, "react"), toFlatConfig(tseslint, "@typescript-eslint"), eslintPluginImport.flatConfigs.recommended, toFlatConfig(eslintPluginLodash, "lodash"), eslintPluginNode.configs["flat/recommended"], { files: ["**/*.ts", "**/*.tsx", "**/*.jsx"], languageOptions: { globals: { ...globals.node, ...browserGlobals, ...globals.es6, ...globals.jest, Atomics: "readonly", SharedArrayBuffer: "readonly", NodeJS: "readonly", // For NodeJS.Timeout, NodeJS.Process, etc. }, parser: eslintParser, parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 2018, sourceType: "module", tsconfigRootDir: projectRoot, project: eslintTsConfig, warnOnUnsupportedTypeScriptVersion: false, // Reuse editor TS service and slim project to reduce memory and speed up. EXPERIMENTAL_useProjectService: true, }, }, plugins: { eslint, "@typescript-eslint": tseslint, lodash: eslintPluginLodash, "no-only-tests": eslintPluginNoOnlyTests, node: eslintPluginNode, react: eslintPluginReact, "react-hooks": eslintPluginReactHooks, "typescript-enum": eslintPluginTypescriptEnum, "unused-imports": eslintPluginUnusedImports, stylistic, local: localRules, }, settings: { react: { version: "detect", }, "import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"], }, "import/resolver": { typescript: { project: eslintTsConfig, alwaysTryTypes: false, }, node: { extensions: [".js", ".jsx", ".ts", ".tsx"], }, }, }, rules: { // TODO: slowly enable no-extraneous-dependencies rule below. For now, it's // enforced only for some packages. // // In an ideal world, the root package.json should have 0 dependencies, and // all packages/* should define their own dependencies by themselves, // independently and locally. The rule below is exactly for that: it ensures // that all package's dependencies are explicitly mentioned in its // package.json, and no dependencies are borrowed implicitly from the root // node_modules. // // In real life though, enforcing packages independency is dangerous: we may // e.g. start accidentally bundle 2 React or 2 Redux versions if we forget // to sync their versions in different monorepo packages' package.json // files. (There must be some other lint rule for this hopefully.) // // In all cases, we should treat node_modules folders content as something // secondary and transient. (It's true even now with the new "yarn // Plug-n-Play" technology which we don't use yet.) The source of truth is // always package.json (enforced by lint) and yarn.lock (defines the exact // contents of all node_modules folders, bit by bit). In this schema, it // doesn't matter at all, does yarn use hoisting or not. // // "import/no-extraneous-dependencies": "error"; "arrow-body-style": ["error", "as-needed"], "lodash/collection-ordering": "off", "lodash/preferred-alias": "off", "lodash/chaining": "off", "lodash/matches-prop-shorthand": "warn", "lodash/prefer-constant": "off", "lodash/prefer-flat-map": "off", "lodash/prefer-immutable-method": "off", "lodash/prefer-includes": "warn", "lodash/prefer-reject": "off", "lodash/identity-shorthand": "off", "lodash/prefer-is-nil": "off", "lodash/prefer-lodash-chain": "off", "lodash/matches-shorthand": "warn", "lodash/prefer-matches": "off", "lodash/prefer-noop": "off", "lodash/prefer-thru": "warn", "lodash/prefer-startswith": "off", "lodash/prefer-lodash-method": "off", "lodash/prefer-lodash-typecheck": "off", "lodash/prop-shorthand": "off", "lodash/import-scope": "off", // Doesn't matter much in node.js. "lodash/unwrap": "off", "node/prefer-global/process": "error", "node/prefer-global/console": "error", "node/prefer-global/buffer": "error", "node/prefer-global/url-search-params": "error", "node/prefer-global/url": "error", // Disable node plugin import checking for TypeScript files since TypeScript handles this. "n/no-deprecated-api": "warn", "n/no-missing-import": "off", "n/no-missing-require": "off", "n/no-extraneous-import": "off", "n/no-extraneous-require": "off", "n/no-process-exit": "off", "n/hashbang": "warn", "n/no-unsupported-features/es-syntax": "off", "n/no-unsupported-features/node-builtins": "off", "n/no-unpublished-import": "off", "require-atomic-updates": "off", "no-prototype-builtins": "off", "react/prop-types": "off", "react/no-unescaped-entities": "off", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/promise-function-async": "error", "@typescript-eslint/await-thenable": "error", "@typescript-eslint/no-floating-promises": [ "error", { ignoreVoid: false }, ], "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-this-alias": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }], "@typescript-eslint/array-type": ["error", { default: "array-simple" }], "@typescript-eslint/ban-ts-comment": ["error"], "@typescript-eslint/no-non-null-asserted-optional-chain": "off", "@typescript-eslint/no-useless-constructor": ["error"], "@typescript-eslint/no-empty-object-type": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-require-imports": "off", "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-unsafe-function-type": "off", "@typescript-eslint/prefer-optional-chain": ["warn"], "@typescript-eslint/consistent-type-imports": ["error"], "@typescript-eslint/return-await": "warn", "@typescript-eslint/require-array-sort-compare": [ "error", { ignoreStringArrays: true }, ], "@typescript-eslint/use-unknown-in-catch-callback-variable": "warn", eqeqeq: ["error"], "object-shorthand": ["error", "always"], "typescript-enum/no-const-enum": ["error"], // not supported in SWC "typescript-enum/no-enum": "off", "@typescript-eslint/naming-convention": [ "error", { selector: "variable", format: ["camelCase", "PascalCase", "UPPER_CASE"], leadingUnderscore: "allow", trailingUnderscore: "allow", filter: { regex: "^__webpack", match: false, }, }, ], // Disable in favour of @typescript-eslint/no-unused-vars. "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": [ "error", { args: "all", vars: "local", // Only check local variables, ignore imports used only as types. argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_", ignoreRestSiblings: true, }, ], // Disable in favour of @typescript-eslint/no-redeclare which understands TS namespaces "no-redeclare": "off", "@typescript-eslint/no-redeclare": "off", "@typescript-eslint/member-ordering": [ "error", { // // ATTENTION: the rules here are not simple, mainly because of this: // https://github.com/typescript-eslint/typescript-eslint/issues/6133 // // Besides that, we also want contradictory things, like: // // 1. Having constructor close to fields definition (because people // often define fields in the constructor arguments), although it // logically should've been below static methods. // 2. Having all abstract things in the class grouped, irregardless on // their public/protected/private modifiers. // default: [ "signature", "call-signature", // Typically, class constants (that's why they're on top). "public-static-field", "public-static-get", "public-static-set", "protected-static-field", "protected-static-get", "protected-static-set", // All concrete fields. What's interesting is that the order we // emotionally want here for properties is private-protected-public, // which is the opposite to the order of methods (which is // public-protected-private). This is likely because the methods are // bulky, and properties are lean. "private-static-field", "private-instance-field", "public-instance-field", "public-abstract-field", "public-abstract-get", "public-abstract-set", // Protected fields and methods are grouped, because eslint currently // doesn't distinguish fields assigned with a lambda FROM methods, and // we often times expose abstract protected overridable lambdas: // https://github.com/typescript-eslint/typescript-eslint/issues/6133 "protected-abstract-field", "protected-abstract-get", "protected-abstract-set", "protected-abstract-method", "public-abstract-method", // the only exception; it's to group all abstract things too "protected-instance-field", "protected-constructor", "protected-static-method", "protected-instance-get", "protected-instance-set", "protected-instance-method", // Public constructor, instance methods, static methods. "public-constructor", // often defines more public/protected/private properties, so should be close to fields "public-static-method", "public-instance-get", "public-instance-set", "public-instance-method", // Private constructor, instance methods, static methods. "private-constructor", "private-static-method", "private-instance-get", "private-instance-set", "private-instance-method", "private-static-get", "private-static-set", ], }, ], "no-constant-condition": ["error", { checkLoops: false }], "no-buffer-constructor": ["error"], "no-console": ["error"], curly: ["error", "all"], "no-case-declarations": "off", "padding-line-between-statements": "off", "stylistic/padding-line-between-statements": [ "error", // Force empty lines. { blankLine: "always", prev: [ "block", "block-like", "function", "class", "interface", "type", ], next: "*", }, { blankLine: "always", prev: "import", next: [ "const", "if", "let", "var", "export", "function", "class", "interface", "type", ], }, { blankLine: "always", prev: "*", next: ["function", "class", "interface", "type"], }, // Allow one-liner functions without extra spacing (hacky): { blankLine: "any", prev: "singleline-const", next: "*" }, { blankLine: "any", prev: "singleline-var", next: "*" }, { blankLine: "any", prev: "singleline-let", next: "*" }, ], "no-restricted-properties": [ "error", { object: "window", property: "location", message: "We use React Router and History to control the location of our web or desktop app. Prefer `useLocation` in React components and `historyFromContext` in Redux Saga.", }, ...(projectRoot.endsWith("client") ? [ { object: "window", property: "document", message: "Please use `useDocument` from `useDocument.tsx`.", }, ...[ "addEventListener", "removeEventListener", "getElementById", "documentElement", "activeElement", "querySelectorAll", ].map((property) => ({ object: "document", property, message: "Please use `useDocument` from `useDocument.tsx`.", })), ] : []), ], "no-restricted-globals": [ "warn", { name: "location", message: "We use React Router and History to control the location of our web or desktop app. Prefer `useLocation` in React components and `historyFromContext` in Redux Saga.", }, ], "no-restricted-syntax": [ "error", { selector: (() => { const RE_BAD = "/([a-z0-9_ ]|^)E[Ii][Dd]|(^|[-_: ])eid/"; return [ `Identifier[name=${RE_BAD}]`, `Literal[value=${RE_BAD}]`, `TemplateElement[value.raw=${RE_BAD}]`, `TSInterfaceDeclaration[id.name=${RE_BAD}]`, ].join(","); })(), message: 'Do not use "eid" or "EID" as a part of a name/field/type. Instead, prefer externalID or external_id.', }, ], "prefer-const": [ "error", { destructuring: "all", }, ], "no-var": "error", "no-void": "error", "react/forbid-dom-props": [ "error", { forbid: [ { propName: "style", message: "Please use CSS Modules instead", }, ], }, ], "react/forbid-component-props": [ "error", { forbid: [ { propName: "style", message: "Please use CSS Modules instead", }, ], }, ], "no-sequences": ["error"], "no-undef": "off", // Too noisy about `react` and other node_modules "import/default": 0, // This complains about React.forwardRef, ReactDOM.render, etc. "import/no-named-as-default-member": 0, // This complains about "apollo" exporting ApolloClient as a default and as a // named import at the same time. "import/no-named-as-default": 0, // Does not seem to work well with node_modules "import/named": 0, "import/newline-after-import": "error", "import/order": [ "error", { groups: ["builtin", "external", "index", "parent", "sibling"], pathGroups: [ { pattern: "./**.module.css", group: "sibling", position: "after", }, { pattern: "./**.module.scss", group: "sibling", position: "after", }, ], alphabetize: { order: "asc", caseInsensitive: true, }, }, ], "import/no-useless-path-segments": ["error", { noUselessIndex: true }], "unused-imports/no-unused-imports": "error", "no-restricted-imports": [ "error", { patterns: [ { group: ["react-router"], message: "Please use react-router-dom instead, since react-router's useLocation() doesn't work properly with StaticRouter on server side.", }, { group: ["braintrust/loadPrompt"], message: "You should use DynamicPromptsRepository to load prompts from braintrust.", } ], }, ], // Fixes a common mistake: `a ?? b < c` which feels like `(a ?? b) < c`, but // actually is `a ?? (b < c)` "no-mixed-operators": [ "error", { allowSamePrecedence: false, groups: [ ["??", "+"], ["??", "-"], ["??", "*"], ["??", "/"], ["??", "%"], ["??", "**"], ["??", "&"], ["??", "|"], ["??", "^"], ["??", "~"], ["??", "<<"], ["??", ">>"], ["??", ">>>"], ["??", "=="], ["??", "!="], ["??", "==="], ["??", "!=="], ["??", ">"], ["??", ">="], ["??", "<"], ["??", "<="], ["??", "&&"], ["??", "||"], ["??", "in"], ["??", "instanceof"], ], }, ], quotes: ["error", "double", { avoidEscape: true }], "no-only-tests/no-only-tests": "error", "local/zod-prefer-safe-nullish": "warn", ...extraRules, }, }, { files: ["**/*.js"], languageOptions: { ecmaVersion: 2022, }, rules: { "n/hashbang": "off", "n/no-unsupported-features/node-builtins": "off", "n/no-extraneous-import": "off", "@typescript-eslint/no-require-imports": "off", "lodash/prefer-lodash-method": "off", "n/no-process-exit": "off", "n/no-missing-require": "off", }, }, { files: ["**/*.mjs"], languageOptions: { sourceType: "module", ecmaVersion: 2020, }, settings: { "import/core-modules": ["@stylistic/eslint-plugin", "@eslint/js"], }, rules: { "n/hashbang": "off", "n/no-extraneous-import": "off", "n/no-unsupported-features/es-syntax": "off", "n/no-unpublished-import": "off", "n/no-process-exit": "off", "lodash/prefer-lodash-method": "off", // Disable import plugin checks for config files to avoid CJS/ESM export parsing issues "import/default": "off", "import/namespace": "off", "import/no-unresolved": "off", "import/no-named-as-default": "off", "import/no-named-as-default-member": "off", }, }, // Disable zod-prefer-safe-nullish for braintrust/eval files since those // schemas are for validating test configuration, not for guiding LLM output { files: [ "**/scripts/evals/**/*.ts", "**/scripts/evals/**/*.tsx", "**/braintrust/**/*.ts", "**/braintrust/**/*.tsx", ], rules: { "local/zod-prefer-safe-nullish": "off", "local/comment-punctuation": "off", // Otherwise, it adds a period after every multiline // commit line. }, }, ]; }