diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index ab0c6d65571d5..9e9a32739aaf1 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -5,7 +5,7 @@ on: - '.github/workflows/push.yml' - '.github/workflows/master.yml' - 'packages/**' - - '.eslintrc.js' + - '.oxlintrc.json' - '.prettierrc' - 'lerna.json' - 'package.json' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 418483142ec66..88d91de95afe8 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -11,7 +11,7 @@ on: - 'rust/cubestore/**' - 'rust/cubesql/**' - 'rust/cube/**' - - '.eslintrc.js' + - '.oxlintrc.json' - '.prettierrc' - 'package.json' - 'lerna.json' @@ -30,7 +30,7 @@ on: - 'rust/cubestore/**' - 'rust/cubesql/**' - 'rust/cube/**' - - '.eslintrc.js' + - '.oxlintrc.json' - '.prettierrc' - 'package.json' - 'lerna.json' @@ -198,8 +198,15 @@ jobs: run: if [ "$(git status | grep nothing)x" = "x" ]; then echo "Non empty changeset after lerna bootstrap"; git status; exit 1; else echo "Nothing to commit. Proceeding"; fi; - name: NPM lint run: yarn lint:npm - - name: Lerna lint - run: yarn lerna run --concurrency 1 lint + - name: Oxlint + run: yarn lint:js + # cubejs-client-ngx has no tests and is not in the root tsconfig references, so + # oxlint is otherwise the only thing that ever looks at it. ng build needs + # @cubejs-client/core's declarations, hence the build:client-core first. + - name: Build client-ngx + run: | + yarn lerna run build:client-core + yarn workspace @cubejs-client/ngx build - name: Cargo fmt cube workspace run: | cargo fmt --manifest-path rust/cube/Cargo.toml --all -- --check diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000000000..0422425479994 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,38 @@ +// The rule set lives in the @cubejs-backend/linter package so it stays a publishable, +// reviewable unit; this file adds the repo-wide ignores. +// +// `yarn lint` runs bare `oxlint`: passing -c/--config would disable nested config +// discovery, and packages/cubejs-client-{dx,react} rely on it. +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "extends": ["./packages/cubejs-linter/.oxlintrc.json"], + "env": { + "node": true, + "es6": true + }, + "ignorePatterns": [ + "dist/", + "lib/", + "build/", + "coverage/", + "gen/", + "**/*.d.ts", + "docs/", + "docs-mintlify/", + "examples/", + "rust/cubesql/", + // never covered by the ESLint setup this replaced + "packages/cubejs-testing/cypress/", + "packages/cubejs-testing/birdbox-fixtures/", + // a mongosh script, not Node: `db` is a shell global + "packages/cubejs-mongobi-driver/test/mongo-init.js", + // vendored Thrift output + "packages/cubejs-hive-driver/idl/", + // generated flatbuffers accessors + "packages/cubejs-cubestore-driver/codegen/", + // generated ANTLR parsers and SQL fixtures + "packages/cubejs-schema-compiler/src/parser/GenericSql*.ts", + "packages/cubejs-schema-compiler/src/parser/Python3*.ts", + "packages/cubejs-schema-compiler/test/unit/fixtures/" + ] +} diff --git a/CLAUDE.md b/CLAUDE.md index 4c5c28bcb2b1d..01799df50ce01 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,9 +29,12 @@ yarn tsc:watch # Clean build artifacts yarn clean -# Run linting across all packages +# Run linting (oxlint over the whole repo) plus the package.json linter yarn lint +# oxlint only +yarn lint:js + # Fix linting issues yarn lint:fix diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 267b6afa0b847..ae11528f5a8d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -206,10 +206,13 @@ Following these guidelines is not a requirement, but you can save some time for ### Code -1. Run `yarn lint` in package before committing your changes. -If package doesn't have lint script, please add it and run. -There's one root `.eslintrc.js` file for all packages except client ones. -Client packages has it's own `.eslintrc.js` files. +1. Run `yarn lint` from the repository root before committing your changes. +Linting is a whole-repo operation now (oxlint covers every package in about a second), +so packages don't carry their own lint scripts. +The shared rule set lives in `packages/cubejs-linter/.oxlintrc.json` and is wired up by the +root `.oxlintrc.json`. A package that needs to diverge gets its own `.oxlintrc.json` +extending the root one - see `packages/cubejs-client-react` for an example. +Use `yarn lint:fix` to apply the fixable violations. 2. Run `yarn test` before committing if package has tests. 3. Please use [conventional commits name](https://www.conventionalcommits.org/) for your PR. It'll be used to build change logs. diff --git a/package.json b/package.json index 2fd7a61812bd9..aee535903c1b5 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,12 @@ "watch": "rollup -c -w", "watch-local": "CUBEJS_API_URL=http://localhost:6020/cubejs-api/v1 rollup -c -w", "lint:npm": "yarn npmPkgJsonLint packages/*/package.json rust/package.json", - "lint": "yarn lint:npm && yarn lerna run lint", - "lint:fix": "lerna run lint:fix", + "lint": "yarn lint:npm && yarn lint:js", + "lint:fix": "oxlint --fix", "tsc": "tsc --build", "tsc:watch": "tsc --build --watch", - "clean": "rimraf packages/*/{tsconfig.tsbuildinfo,lib,dist} packages/cubejs-testing/cypress/{tsconfig.tsbuildinfo,dist}" + "clean": "rimraf packages/*/{tsconfig.tsbuildinfo,lib,dist} packages/cubejs-testing/cypress/{tsconfig.tsbuildinfo,dist}", + "lint:js": "oxlint" }, "author": "Cube Dev, Inc.", "dependencies": { @@ -46,14 +47,12 @@ "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^11.2.0", - "@typescript-eslint/eslint-plugin": "^8.46.0", - "@typescript-eslint/parser": "^8.46.0", - "eslint": "^8.57.1", "@types/fs-extra": "^11.0.4", "@types/jest": "^29", "husky": "^5.0.4", "is-ci": "^2.0.0", "npm-package-json-lint": "^5.1.0", + "oxlint": "^1.82.0", "postcss": "^8.2.8", "prettier": "^2.0.5", "rimraf": "^3.0.2", diff --git a/packages/cubejs-api-gateway/.eslintignore b/packages/cubejs-api-gateway/.eslintignore deleted file mode 100644 index 53c37a16608c0..0000000000000 --- a/packages/cubejs-api-gateway/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -dist \ No newline at end of file diff --git a/packages/cubejs-api-gateway/package.json b/packages/cubejs-api-gateway/package.json index d1516e8a821d7..5c6dc8a1a251b 100644 --- a/packages/cubejs-api-gateway/package.json +++ b/packages/cubejs-api-gateway/package.json @@ -18,9 +18,7 @@ "unit": "CUBE_JS_NATIVE_API_GATEWAY_INTERNAL=true jest --coverage --forceExit dist/test", "build": "rm -rf dist && npm run tsc", "tsc": "tsc", - "watch": "tsc -w", - "lint": "eslint \"**/*.{ts,tsx}\"", - "lint:fix": "eslint --fix \"**/*.{ts,tsx}\"" + "watch": "tsc -w" }, "files": [ "README.md", @@ -69,8 +67,5 @@ "supertest": "^4.0.2", "typescript": "~6.0.3" }, - "license": "Apache-2.0", - "eslintConfig": { - "extends": "../cubejs-linter" - } + "license": "Apache-2.0" } diff --git a/packages/cubejs-api-gateway/src/gateway.ts b/packages/cubejs-api-gateway/src/gateway.ts index 1730d5cfbb22c..8b27f06b68a53 100644 --- a/packages/cubejs-api-gateway/src/gateway.ts +++ b/packages/cubejs-api-gateway/src/gateway.ts @@ -112,11 +112,11 @@ import { } from './helpers/transform-meta-extended'; type HandleErrorOptions = { - e: any, - res: ResponseResultFn, - context?: any, - query?: any, - requestStarted?: Date + e: any, + res: ResponseResultFn, + context?: any, + query?: any, + requestStarted?: Date }; function userAsyncHandler(handler: (req: Request & { context: ExtendedRequestContext }, res: ExpressResponse) => Promise) { @@ -1525,7 +1525,7 @@ class ApiGateway { disablePostProcessing, context, res, - }: {query: string, disablePostProcessing: boolean} & BaseRequest) { + }: { query: string, disablePostProcessing: boolean } & BaseRequest) { try { await this.assertApiScope('sql', context.securityContext); diff --git a/packages/cubejs-api-gateway/src/interfaces.ts b/packages/cubejs-api-gateway/src/interfaces.ts index 5094494417ef2..a7cd8a17f6531 100644 --- a/packages/cubejs-api-gateway/src/interfaces.ts +++ b/packages/cubejs-api-gateway/src/interfaces.ts @@ -92,11 +92,11 @@ export { * Context rejection middleware. */ export type ContextRejectionMiddlewareFn = - ( - req: Request, - res: ExpressResponse, - next: ExpressNextFunction, - ) => void; + ( + req: Request, + res: ExpressResponse, + next: ExpressNextFunction, + ) => void; type ContextAcceptorResult = { accepted: boolean; rejectMessage?: any }; diff --git a/packages/cubejs-api-gateway/src/types/auth.ts b/packages/cubejs-api-gateway/src/types/auth.ts index 4b7d11ff839f6..600cc789efcb0 100644 --- a/packages/cubejs-api-gateway/src/types/auth.ts +++ b/packages/cubejs-api-gateway/src/types/auth.ts @@ -40,7 +40,7 @@ interface JWTOptions { } type CheckAuthResponse = { - 'security_context'?: unknown, + security_context?: unknown, }; /** @@ -87,7 +87,7 @@ type CanSwitchSQLUserFn = */ type ContextToApiScopesFn = (securityContext?: any, scopes?: ApiScopesTuple) => - Promise; + Promise; export { CheckAuthInternalOptions, diff --git a/packages/cubejs-api-gateway/src/types/query.ts b/packages/cubejs-api-gateway/src/types/query.ts index 7b42fd7c046d7..6848b9de4292e 100644 --- a/packages/cubejs-api-gateway/src/types/query.ts +++ b/packages/cubejs-api-gateway/src/types/query.ts @@ -42,9 +42,9 @@ type LogicalOrFilter = { export type GroupingSetType = 'Rollup' | 'Cube'; type GroupingSet = { - groupType: GroupingSetType, - id: number, - subId?: null | number + groupType: GroupingSetType, + id: number, + subId?: null | number }; export type EvalPatchMeasureFilterExpression = { diff --git a/packages/cubejs-api-gateway/src/types/request.ts b/packages/cubejs-api-gateway/src/types/request.ts index 9b78a1afda503..99c4a75d9e9d1 100644 --- a/packages/cubejs-api-gateway/src/types/request.ts +++ b/packages/cubejs-api-gateway/src/types/request.ts @@ -165,7 +165,7 @@ type SqlApiRequest = BaseRequest & { * Pre-aggregations selector object. */ type PreAggsSelector = { - contexts: {securityContext: any}[], + contexts: { securityContext: any }[], timezones: string[], dataSources?: string[], cubes?: string[], @@ -178,7 +178,7 @@ type PreAggsSelector = { */ type PreAggJob = { request: string; - context: {securityContext: any}; + context: { securityContext: any }; preagg: string; table: string; target: string; diff --git a/packages/cubejs-api-gateway/test/helpers/transform-meta-extended.test.ts b/packages/cubejs-api-gateway/test/helpers/transform-meta-extended.test.ts index ecbefc788da16..68e690e07ddba 100644 --- a/packages/cubejs-api-gateway/test/helpers/transform-meta-extended.test.ts +++ b/packages/cubejs-api-gateway/test/helpers/transform-meta-extended.test.ts @@ -42,12 +42,12 @@ const MOCK_USERS_CUBE = { plan: { case: { when: { - '0': { + 0: { // eslint-disable-next-line quotes sql: () => `tenantEnterpriseFlag = true`, label: 'Enterprise', }, - '1': { + 1: { // eslint-disable-next-line quotes sql: () => `stripe_customer_id IS NOT NULL`, label: 'Standard', diff --git a/packages/cubejs-api-gateway/test/normalize-query-filters-dates.test.js b/packages/cubejs-api-gateway/test/normalize-query-filters-dates.test.js index 8501b77892d48..f4a0bee97116d 100644 --- a/packages/cubejs-api-gateway/test/normalize-query-filters-dates.test.js +++ b/packages/cubejs-api-gateway/test/normalize-query-filters-dates.test.js @@ -396,7 +396,7 @@ describe('normalizeQuery: date-range filter resolution', () => { timezone: 'UTC', filters: [{ or: [ { member: 'Orders.createdAt', operator: 'inDateRange', values: ['today'] }, - ]}], + ] }], }, false); const la = normalizeQuery({ @@ -404,7 +404,7 @@ describe('normalizeQuery: date-range filter resolution', () => { timezone: 'America/Los_Angeles', filters: [{ or: [ { member: 'Orders.createdAt', operator: 'inDateRange', values: ['today'] }, - ]}], + ] }], }, false); expect(utc.filters[0].or[0].values[0]).toMatch(/^2026-06-25T/); diff --git a/packages/cubejs-athena-driver/package.json b/packages/cubejs-athena-driver/package.json index 6b64e6bd40961..55e0d6343c19c 100644 --- a/packages/cubejs-athena-driver/package.json +++ b/packages/cubejs-athena-driver/package.json @@ -18,9 +18,7 @@ "test": "yarn integration", "unit": "NODE_OPTIONS=--experimental-vm-modules jest --verbose dist/test/unit", "integration": "npm run integration:athena", - "integration:athena": "NODE_OPTIONS=--experimental-vm-modules jest --verbose dist/test", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:athena": "NODE_OPTIONS=--experimental-vm-modules jest --verbose dist/test" }, "files": [ "dist/src", @@ -45,9 +43,6 @@ "publishConfig": { "access": "public" }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "jest": { "testEnvironment": "node" }, diff --git a/packages/cubejs-athena-driver/test/AthenaDriver.test.ts b/packages/cubejs-athena-driver/test/AthenaDriver.test.ts index 1ac567dc5ac02..8e4ffd82e4741 100644 --- a/packages/cubejs-athena-driver/test/AthenaDriver.test.ts +++ b/packages/cubejs-athena-driver/test/AthenaDriver.test.ts @@ -78,7 +78,7 @@ describe('AthenaDriver', () => { // Aggressive pollTimeout (5s) so the test doesn't depend on the // ambient CUBEJS_DB_QUERY_TIMEOUT. Constructor multiplies by 1000. const cancelDriver = new AthenaDriver({ pollTimeout: 5 }); - const athena = (cancelDriver as any).athena; + const { athena } = (cancelDriver as any); const startOriginal = athena.startQueryExecution.bind(athena); let queryExecutionId = ''; diff --git a/packages/cubejs-backend-cloud/package.json b/packages/cubejs-backend-cloud/package.json index af658e9a7c623..5d764675ca6f6 100644 --- a/packages/cubejs-backend-cloud/package.json +++ b/packages/cubejs-backend-cloud/package.json @@ -9,9 +9,7 @@ "tsc": "tsc", "watch": "tsc -w", "test": "npm run unit", - "unit": "jest dist/test", - "lint": "eslint --debug src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "unit": "jest dist/test" }, "files": [ "README.md", @@ -49,8 +47,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-backend-cloud/src/cloud.ts b/packages/cubejs-backend-cloud/src/cloud.ts index e0c36180c3819..7ed8eb860e614 100644 --- a/packages/cubejs-backend-cloud/src/cloud.ts +++ b/packages/cubejs-backend-cloud/src/cloud.ts @@ -109,7 +109,7 @@ export class CubeCloudClient { public uploadFile( { transaction, fileName, data, auth }: - { transaction: any, fileName: string, data: ReadStream, auth?: AuthObject } + { transaction: any, fileName: string, data: ReadStream, auth?: AuthObject } ) { const formData = new FormData(); formData.append('transaction', JSON.stringify(transaction)); @@ -134,7 +134,7 @@ export class CubeCloudClient { } public finishUpload({ transaction, files, auth }: - { transaction: any, files: any, auth?: AuthObject }) { + { transaction: any, files: any, auth?: AuthObject }) { return this.request({ url: (deploymentId: string) => `build/deploy/${deploymentId}/finish-upload${this.extendRequestByLivePreview()}`, method: 'POST', @@ -159,7 +159,7 @@ export class CubeCloudClient { }); } - public getStatusDevMode({ auth, lastHash }: { auth?: AuthObject, lastHash?: string } = {}): Promise<{[key: string]: any}> { + public getStatusDevMode({ auth, lastHash }: { auth?: AuthObject, lastHash?: string } = {}): Promise<{ [key: string]: any }> { const params = new URLSearchParams(); if (lastHash) { params.append('lastHash', lastHash); diff --git a/packages/cubejs-backend-cloud/src/deploy.ts b/packages/cubejs-backend-cloud/src/deploy.ts index bd6142bc648ce..e9e334aacec42 100644 --- a/packages/cubejs-backend-cloud/src/deploy.ts +++ b/packages/cubejs-backend-cloud/src/deploy.ts @@ -66,7 +66,7 @@ export class DeployDirectory { type DeployHooks = { onStart?: (deploymentName: string, files: string[]) => void, - onUpdate?: (i: number, { file }: { file: string}) => void, + onUpdate?: (i: number, { file }: { file: string }) => void, onUpload?: (files: string[], file: string) => void, onFinally?: () => void }; diff --git a/packages/cubejs-backend-maven/package.json b/packages/cubejs-backend-maven/package.json index 2a05e0d7b0f5b..cb1e372b432cf 100644 --- a/packages/cubejs-backend-maven/package.json +++ b/packages/cubejs-backend-maven/package.json @@ -21,9 +21,7 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "unit:disabled-for-ci": "jest dist/test/*.js", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "unit:disabled-for-ci": "jest dist/test/*.js" }, "files": [ "README.md", @@ -44,8 +42,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-backend-maven/src/maven.ts b/packages/cubejs-backend-maven/src/maven.ts index 47c58a269ca38..a4f76827639d5 100644 --- a/packages/cubejs-backend-maven/src/maven.ts +++ b/packages/cubejs-backend-maven/src/maven.ts @@ -27,10 +27,17 @@ export function generateXml(dependencies: MavenDependency[]) { 'xsi:schemaLocation': 'http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd', }) // project-> - .ele('modelVersion').txt('4.0.0').up() - .ele('groupId').txt('com.mycompany.app').up() - .ele('artifactId').txt('my-app').up() - .ele('version').txt('1.0-SNAPSHOT').up() + .ele('modelVersion').txt('4.0.0') + .up() + .ele('groupId') + .txt('com.mycompany.app') + .up() + .ele('artifactId') + .txt('my-app') + .up() + .ele('version') + .txt('1.0-SNAPSHOT') + .up() // // project->properties // .ele('properties') // .ele('maven.compiler.source').txt('1.8').up() diff --git a/packages/cubejs-backend-native/benchmarks/python-config.bench.ts b/packages/cubejs-backend-native/benchmarks/python-config.bench.ts index 5c648123bcaf7..6e1318b0936c8 100644 --- a/packages/cubejs-backend-native/benchmarks/python-config.bench.ts +++ b/packages/cubejs-backend-native/benchmarks/python-config.bench.ts @@ -82,7 +82,7 @@ describe('Python Configuration Loading', () => { // It should help to identify any potential issues with GIL 'checkAuth - sync version (parallel 50x)': async () => { await Promise.all( - Array.from({ length: 50 }, () => configPy.checkAuth!({ requestId: 'sync-bench' }, 'SYNC_TOKEN')) + Array.from({ length: 50 }, () => configPy.checkAuth!({ requestId: 'sync-bench' }, 'SYNC_TOKEN')) ); }, @@ -93,7 +93,7 @@ describe('Python Configuration Loading', () => { // It should help to identify any potential issues with GIL 'checkAuth - async version (parallel 50x)': async () => { await Promise.all( - Array.from({ length: 50 }, () => configAsyncPy.checkAuth!({ requestId: 'async-bench' }, 'ASYNC_TOKEN')) + Array.from({ length: 50 }, () => configAsyncPy.checkAuth!({ requestId: 'async-bench' }, 'ASYNC_TOKEN')) ); }, @@ -119,4 +119,4 @@ describe('Python Configuration Loading', () => { await configAsyncPy.queryRewrite!(testQuery, {}); }, }); -}); \ No newline at end of file +}); diff --git a/packages/cubejs-backend-native/js/index.ts b/packages/cubejs-backend-native/js/index.ts index a82b172cb4bfc..bd939ef15ff68 100644 --- a/packages/cubejs-backend-native/js/index.ts +++ b/packages/cubejs-backend-native/js/index.ts @@ -151,7 +151,7 @@ export type DBResponsePrimitive = // TODO type this better, to make it proper disjoint union export type Sql4SqlOk = { sql: string, - values: Array, + values: Array, }; export type Sql4SqlError = { error: string }; export type Sql4SqlCommon = { @@ -530,7 +530,7 @@ export const transpileYaml = async (transpileRequests: TransformConfig[]): Promi export interface PyConfiguration { repositoryFactory?: (ctx: unknown) => Promise, logger?: (msg: string, params: Record) => void, - checkAuth?: (req: unknown, authorization: string) => Promise<{ 'security_context'?: unknown }> + checkAuth?: (req: unknown, authorization: string) => Promise<{ security_context?: unknown }> extendContext?: (req: unknown) => Promise queryRewrite?: (query: unknown, ctx: unknown) => Promise contextToApiScopes?: () => Promise diff --git a/packages/cubejs-backend-native/package.json b/packages/cubejs-backend-native/package.json index c206945317f14..389ae10c2be5c 100644 --- a/packages/cubejs-backend-native/package.json +++ b/packages/cubejs-backend-native/package.json @@ -28,9 +28,7 @@ "test:unit": "yarn run unit", "test:bridge": "npm run native:build-debug-bridge-tests && npm run tsc && jest --config jest-bridge.config.js --forceExit", "test:cargo": "cargo test", - "bench": "jest --config jest-bench.config.js --forceExit", - "lint": "eslint test/ js/ --ext .ts", - "lint:fix": "eslint --fix test/ js/ --ext .ts" + "bench": "jest --config jest-bench.config.js --forceExit" }, "engines": { "node": ">=20.0.0" @@ -84,9 +82,6 @@ } ] }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cubejs-backend-native/test/server.js b/packages/cubejs-backend-native/test/server.js index 00e992c6b8a92..1e3c8adb675ff 100644 --- a/packages/cubejs-backend-native/test/server.js +++ b/packages/cubejs-backend-native/test/server.js @@ -138,5 +138,6 @@ const meta_fixture = require('./meta'); }); // block + // eslint-disable-next-line no-empty-function await new Promise(() => {}); })(); diff --git a/packages/cubejs-backend-shared/package.json b/packages/cubejs-backend-shared/package.json index 441076682943c..edda4cfdcc3c9 100644 --- a/packages/cubejs-backend-shared/package.json +++ b/packages/cubejs-backend-shared/package.json @@ -8,8 +8,6 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts", "unit": "jest --coverage" }, "files": [ @@ -61,9 +59,6 @@ "publishConfig": { "access": "public" }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "resolutions": { "colors": "1.4.0" } diff --git a/packages/cubejs-backend-shared/src/promises.ts b/packages/cubejs-backend-shared/src/promises.ts index 45c7b1ec1e33b..e29bc66645865 100644 --- a/packages/cubejs-backend-shared/src/promises.ts +++ b/packages/cubejs-backend-shared/src/promises.ts @@ -246,24 +246,24 @@ export const retryWithTimeout = ( fn: (token: CancelToken) => Promise, { timeout, intervalPause }: RetryWithTimeoutOptions, ) => withTimeoutRace( - createCancelablePromise(async (token) => { - let i = 0; + createCancelablePromise(async (token) => { + let i = 0; - while (!token.isCanceled()) { - i++; + while (!token.isCanceled()) { + i++; - const result = await fn(token); - if (result) { - return result; - } - - await token.with(pausePromise(intervalPause(i))); + const result = await fn(token); + if (result) { + return result; } - return null; - }), - timeout - ); + await token.with(pausePromise(intervalPause(i))); + } + + return null; + }), + timeout +); export type AsyncDebounceOptions = { max?: number; diff --git a/packages/cubejs-backend-shared/test/db_env_pre_aggregations.test.ts b/packages/cubejs-backend-shared/test/db_env_pre_aggregations.test.ts index 7b294ba7aa455..7542e81c63f9c 100644 --- a/packages/cubejs-backend-shared/test/db_env_pre_aggregations.test.ts +++ b/packages/cubejs-backend-shared/test/db_env_pre_aggregations.test.ts @@ -135,7 +135,6 @@ describe('Pre-aggregation env vars (multi datasource)', () => { .toEqual('default-host'); }); - test('keyByDataSource with preAggregations for named datasource', () => { expect(keyByDataSource('CUBEJS_DB_HOST', 'analytics', true)) .toEqual('CUBEJS_DS_ANALYTICS_PRE_AGGREGATIONS_DB_HOST'); diff --git a/packages/cubejs-backend-shared/test/db_env_single.test.ts b/packages/cubejs-backend-shared/test/db_env_single.test.ts index adc04585701fb..b24e709bd2ce4 100644 --- a/packages/cubejs-backend-shared/test/db_env_single.test.ts +++ b/packages/cubejs-backend-shared/test/db_env_single.test.ts @@ -1038,12 +1038,12 @@ describe('Single datasources', () => { }); test('getEnv("fireboltAccount")', () => { - process.env.CUBEJS_FIREBOLT_ACCOUNT = "default1"; + process.env.CUBEJS_FIREBOLT_ACCOUNT = 'default1'; expect(getEnv('fireboltAccount', { dataSource: 'default' })).toEqual('default1'); expect(getEnv('fireboltAccount', { dataSource: 'postgres' })).toEqual('default1'); expect(getEnv('fireboltAccount', { dataSource: 'wrong' })).toEqual('default1'); - process.env.CUBEJS_FIREBOLT_ACCOUNT = "default2"; + process.env.CUBEJS_FIREBOLT_ACCOUNT = 'default2'; expect(getEnv('fireboltAccount', { dataSource: 'default' })).toEqual('default2'); expect(getEnv('fireboltAccount', { dataSource: 'postgres' })).toEqual('default2'); expect(getEnv('fireboltAccount', { dataSource: 'wrong' })).toEqual('default2'); diff --git a/packages/cubejs-backend-shared/test/disposedProxy.test.ts b/packages/cubejs-backend-shared/test/disposedProxy.test.ts index 3810bb3a11983..e82d3419f9764 100644 --- a/packages/cubejs-backend-shared/test/disposedProxy.test.ts +++ b/packages/cubejs-backend-shared/test/disposedProxy.test.ts @@ -43,7 +43,7 @@ describe('disposedProxy', () => { const proxy = disposedProxy('testProperty', 'test instance'); expect(() => 'someProperty' in proxy).toThrow( - "Cannot check property existence on test instance. " + + 'Cannot check property existence on test instance. ' + "The 'testProperty' has been cleaned up and is no longer available." ); }); @@ -52,7 +52,7 @@ describe('disposedProxy', () => { const proxy = disposedProxy('testProperty', 'test instance'); expect(() => Object.keys(proxy)).toThrow( - "Cannot enumerate properties on test instance. " + + 'Cannot enumerate properties on test instance. ' + "The 'testProperty' has been cleaned up and is no longer available." ); }); @@ -61,7 +61,7 @@ describe('disposedProxy', () => { const proxy = disposedProxy('testProperty', 'test instance'); expect(() => Object.getPrototypeOf(proxy)).toThrow( - "Cannot get prototype of test instance. " + + 'Cannot get prototype of test instance. ' + "The 'testProperty' has been cleaned up and is no longer available." ); }); diff --git a/packages/cubejs-backend-shared/test/http-utils.test.ts b/packages/cubejs-backend-shared/test/http-utils.test.ts index 50e08d1de80be..100bf338e0f72 100644 --- a/packages/cubejs-backend-shared/test/http-utils.test.ts +++ b/packages/cubejs-backend-shared/test/http-utils.test.ts @@ -86,6 +86,7 @@ describe('extractArchive', () => { // producer capable of recording a symlink emits — with the default 0 (MS-DOS) // the external-attributes field is formally DOS attribute bits and the unix // mode below is not meant to be read at all. + // eslint-disable-next-line no-bitwise cdh.writeUInt16LE((3 << 8) | 20, 4); cdh.writeUInt16LE(10, 6); // version needed cdh.writeUInt16LE(0, 10); // method: stored @@ -96,6 +97,7 @@ describe('extractArchive', () => { // External attributes carry the unix mode in the high 16 bits, which is how a // zip records a symlink (`0o120000`). `>>> 0` because the shift overflows into a // negative signed int32 otherwise. + // eslint-disable-next-line no-bitwise cdh.writeUInt32LE((((entry.mode ?? 0o100644) << 16) >>> 0), 38); cdh.writeUInt32LE(offset, 42); // relative offset of local header central.push(cdh, name); diff --git a/packages/cubejs-backend-shared/test/package.test.ts b/packages/cubejs-backend-shared/test/package.test.ts index 99c254b11c854..660f821c0c25d 100644 --- a/packages/cubejs-backend-shared/test/package.test.ts +++ b/packages/cubejs-backend-shared/test/package.test.ts @@ -10,5 +10,5 @@ test('isSslKey', () => { expect(isSslKey(`-----BEGIN RSA PRIVATE KEY-----\nAbcDEF\n-----END RSA PRIVATE KEY-----`)).toBe(true); expect(isSslKey(`-----BEGIN EC PRIVATE KEY-----\nAbcDEF\n-----END EC PRIVATE KEY-----`)).toBe(true); expect(isSslKey(`-----BEGIN PRIVATE KEY-----\nAbcDEF\n-----END PRIVATE KEY-----`)).toBe(false); - expect(isSslKey('./file.path')).toBe(false) -}) \ No newline at end of file + expect(isSslKey('./file.path')).toBe(false); +}); diff --git a/packages/cubejs-base-driver/package.json b/packages/cubejs-base-driver/package.json index e5a49a15b90c1..16c3d4efdfb41 100644 --- a/packages/cubejs-base-driver/package.json +++ b/packages/cubejs-base-driver/package.json @@ -18,9 +18,7 @@ "tsc": "tsc", "watch": "tsc -w", "test": "npm run unit && npm run integration", - "unit": "NODE_OPTIONS=--experimental-vm-modules jest --runInBand --coverage --verbose test/unit", - "lint": "eslint src/* test/* --ext .ts,.js", - "lint:fix": "eslint --fix src/* test/* --ext .ts,.js" + "unit": "NODE_OPTIONS=--experimental-vm-modules jest --runInBand --coverage --verbose test/unit" }, "files": [ "README.md", @@ -45,9 +43,6 @@ "typescript": "~6.0.3" }, "license": "Apache-2.0", - "eslintConfig": { - "extends": "../cubejs-linter" - }, "publishConfig": { "access": "public" } diff --git a/packages/cubejs-base-driver/src/BaseDriver.ts b/packages/cubejs-base-driver/src/BaseDriver.ts index bdb95de93dc98..4e054f5587772 100644 --- a/packages/cubejs-base-driver/src/BaseDriver.ts +++ b/packages/cubejs-base-driver/src/BaseDriver.ts @@ -635,7 +635,7 @@ export abstract class BaseDriver implements DriverInterface { return Date.now(); } - public wrapQueryWithLimit(query: { query: string, limit: number}) { + public wrapQueryWithLimit(query: { query: string, limit: number }) { query.query = `SELECT * FROM (${query.query}) AS t LIMIT ${query.limit}`; } diff --git a/packages/cubejs-bigquery-driver/package.json b/packages/cubejs-bigquery-driver/package.json index 9d30aaa82408a..4c02bde60a225 100644 --- a/packages/cubejs-bigquery-driver/package.json +++ b/packages/cubejs-bigquery-driver/package.json @@ -17,9 +17,7 @@ "watch": "tsc -w", "test": "yarn integration", "integration": "npm run integration:bigquery", - "integration:bigquery": "jest --verbose dist/test", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:bigquery": "jest --verbose dist/test" }, "files": [ "dist/src", @@ -49,8 +47,5 @@ "license": "Apache-2.0", "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-cli/package.json b/packages/cubejs-cli/package.json index ebdf5b36a72ea..fd0e880627640 100644 --- a/packages/cubejs-cli/package.json +++ b/packages/cubejs-cli/package.json @@ -19,9 +19,7 @@ "tsc": "tsc", "watch": "tsc -w", "test": "npm run unit", - "unit": "jest dist/test", - "lint": "eslint src/* --ext .ts,.js", - "lint:fix": "eslint --fix src/* --ext .ts,.js" + "unit": "jest dist/test" }, "files": [ "dist/src/*", @@ -64,8 +62,5 @@ "jest": "^29", "typescript": "~6.0.3" }, - "license": "Apache-2.0", - "eslintConfig": { - "extends": "../cubejs-linter" - } + "license": "Apache-2.0" } diff --git a/packages/cubejs-clickhouse-driver/package.json b/packages/cubejs-clickhouse-driver/package.json index f1896ca78b8df..c62680b29e513 100644 --- a/packages/cubejs-clickhouse-driver/package.json +++ b/packages/cubejs-clickhouse-driver/package.json @@ -21,8 +21,6 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "lint": "eslint src/* test/* --ext .ts", - "lint:fix": "eslint --fix src/* test/* --ext .ts", "unit": "NODE_OPTIONS=--experimental-vm-modules jest dist/test/unit", "integration": "NODE_OPTIONS=--experimental-vm-modules jest dist/test/integration", "integration:clickhouse": "NODE_OPTIONS=--experimental-vm-modules jest dist/test/integration" @@ -45,8 +43,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-client-core/package.json b/packages/cubejs-client-core/package.json index 4fd3f55717d8e..629f7d7258c62 100644 --- a/packages/cubejs-client-core/package.json +++ b/packages/cubejs-client-core/package.json @@ -49,9 +49,7 @@ "watch": "tsc -w", "test": "npm run unit", "unit": "vitest run --coverage", - "bench": "vitest bench", - "lint": "eslint src/* test/ --ext .ts,.js", - "lint:fix": "eslint --fix src/* test/ --ext .ts,js" + "bench": "vitest bench" }, "files": [ "dist", @@ -67,8 +65,5 @@ "@vitest/coverage-v8": "^4", "typescript": "~6.0.3", "vitest": "^4" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-client-dx/.eslintrc.js b/packages/cubejs-client-dx/.eslintrc.js deleted file mode 100644 index 947ccc6f5d58a..0000000000000 --- a/packages/cubejs-client-dx/.eslintrc.js +++ /dev/null @@ -1,39 +0,0 @@ -module.exports = { - extends: 'airbnb-base', - plugins: [ - 'import' - ], - parser: '@babel/eslint-parser', - rules: { - 'max-classes-per-file': 0, - 'prefer-object-spread': 0, - 'import/no-unresolved': 0, - 'comma-dangle': 0, - 'no-console': 0, - 'arrow-parens': 0, - 'import/extensions': 0, - quotes: ['warn', 'single'], - 'no-prototype-builtins': 0, - 'class-methods-use-this': 0, - 'no-param-reassign': 0, - 'no-mixed-operators': 0, - 'no-else-return': 0, - 'prefer-promise-reject-errors': 0, - 'no-plusplus': 0, - 'no-await-in-loop': 0, - 'operator-linebreak': 0, - 'max-len': ['error', 120, 2, { - ignoreUrls: true, - ignoreComments: false, - ignoreRegExpLiterals: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - }], - 'no-trailing-spaces': ['warn', { skipBlankLines: true }], - 'no-unused-vars': ['warn'], - 'object-curly-newline': 0 - }, - // env: { - // 'jest/globals': true - // } -}; diff --git a/packages/cubejs-client-dx/.oxlintrc.json b/packages/cubejs-client-dx/.oxlintrc.json new file mode 100644 index 0000000000000..3d1570836f24a --- /dev/null +++ b/packages/cubejs-client-dx/.oxlintrc.json @@ -0,0 +1,30 @@ +// Delta from the repo-root config. This package predates the shared linter config and +// its own ESLint setup pinned eslint-config-airbnb-base 13 while everything else was on +// 14; extending the root config normalises it onto 14. Only the divergences this +// package actually asked for are restated below. +{ + "$schema": "../../node_modules/oxlint/configuration_schema.json", + "extends": ["../../.oxlintrc.json"], + "env": { + "node": true, + "es6": true + }, + "ignorePatterns": ["dist/", "lib/", "coverage/", "**/*.d.ts"], + "rules": { + "@stylistic/max-len": [ + "error", + 120, + 2, + { + "ignoreUrls": true, + "ignoreComments": false, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + "import/prefer-default-export": "error", + "no-underscore-dangle": "error", + "no-useless-constructor": "error" + } +} diff --git a/packages/cubejs-client-dx/package.json b/packages/cubejs-client-dx/package.json index 4c8a6fb8903be..11ddbeb3d101c 100644 --- a/packages/cubejs-client-dx/package.json +++ b/packages/cubejs-client-dx/package.json @@ -14,9 +14,7 @@ "author": "Cube Dev, Inc.", "scripts": { "test": "npm run unit", - "unit": "jest --passWithNoTests", - "lint": "eslint src/*.js", - "lint:fix": "eslint --fix src/*.js" + "unit": "jest --passWithNoTests" }, "files": [ "src", @@ -27,14 +25,9 @@ "license": "MIT", "devDependencies": { "@babel/core": "^7.24.5", - "@babel/eslint-parser": "^7", "@babel/preset-env": "^7.24.5", "@types/jest": "^29", "babel-jest": "^29", - "eslint": "^7.21.0", - "eslint-config-airbnb-base": "^13.1.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^10.0.0", "jest": "^29" }, "publishConfig": { diff --git a/packages/cubejs-client-ngx/.prettierrc b/packages/cubejs-client-ngx/.prettierrc deleted file mode 100644 index a5d7bd5bab16f..0000000000000 --- a/packages/cubejs-client-ngx/.prettierrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "printWidth": 80, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "arrowParens": "always", - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false, - "overrides": [ - { - "files": ["*.css", "*.scss"], - "options": { - "singleQuote": false - } - } - ] -} diff --git a/packages/cubejs-client-ngx/index.ts b/packages/cubejs-client-ngx/index.ts index 55dfa84373c96..cdb23e58e95c2 100644 --- a/packages/cubejs-client-ngx/index.ts +++ b/packages/cubejs-client-ngx/index.ts @@ -3,4 +3,4 @@ // replaces this file with production index.ts when it rewrites private symbol // names. -export * from './src/public_api'; \ No newline at end of file +export * from './src/public_api'; diff --git a/packages/cubejs-client-ngx/src/client.ts b/packages/cubejs-client-ngx/src/client.ts index 0c86ea53bd411..07ebbe79665d7 100644 --- a/packages/cubejs-client-ngx/src/client.ts +++ b/packages/cubejs-client-ngx/src/client.ts @@ -22,7 +22,7 @@ export class CubeClient { private cubeApi: CubeApi; - constructor(@Inject('config') private config: any | Observable) { + public constructor(@Inject('config') private config: any | Observable) { if (this.config instanceof Observable) { this.config.subscribe(() => { this.ready$.next(true); @@ -56,7 +56,7 @@ export class CubeClient { query: Query | Query[], options?: LoadMethodOptions ): Observable> { - return from(>>this.apiInstance().load(query, options)); + return from(this.apiInstance().load(query, options) as Promise>); } public sql( @@ -78,18 +78,15 @@ export class CubeClient { } public watch(query, params = {}): Observable> { - return new Observable((observer) => - query.subscribe({ - next: async (query) => { - try { - const resultSet = await this.apiInstance().load(query, params); - observer.next(resultSet); - } catch(err) { - observer.error(err); - } - - }, - }) - ); + return new Observable((observer) => query.subscribe({ + next: async (currentQuery) => { + try { + const resultSet = await this.apiInstance().load(currentQuery, params); + observer.next(resultSet); + } catch (err) { + observer.error(err); + } + }, + })); } } diff --git a/packages/cubejs-client-ngx/src/query-builder/builder-meta.ts b/packages/cubejs-client-ngx/src/query-builder/builder-meta.ts index b57dba6bd4f96..3638744939936 100644 --- a/packages/cubejs-client-ngx/src/query-builder/builder-meta.ts +++ b/packages/cubejs-client-ngx/src/query-builder/builder-meta.ts @@ -6,33 +6,36 @@ import { } from '@cubejs-client/core'; export class BuilderMeta { - measures: TCubeMeasure[]; - dimensions: TCubeDimension[]; - segments: TCubeSegment[]; - timeDimensions: TCubeDimension[]; - filters: Array; + public measures: TCubeMeasure[]; - constructor(public readonly meta: Meta) { + public dimensions: TCubeDimension[]; + + public segments: TCubeSegment[]; + + public timeDimensions: TCubeDimension[]; + + public filters: Array; + + public constructor(public readonly meta: Meta) { this.mapMeta(); } private mapMeta() { - const allDimensions = ( - this.meta.membersForQuery(null, 'dimensions') - ); + const allDimensions = this.meta.membersForQuery( + null, + 'dimensions' + ) as TCubeDimension[]; - this.measures = this.meta.membersForQuery(null, 'measures'); + this.measures = this.meta.membersForQuery(null, 'measures') as TCubeMeasure[]; this.segments = this.meta.membersForQuery(null, 'segments'); this.dimensions = allDimensions.filter(({ type }) => type !== 'time'); this.timeDimensions = allDimensions.filter(({ type }) => type === 'time'); - this.filters = [...allDimensions, ...this.measures].map((member) => { - return { - ...member, - operators: this.meta.filterOperatorsForMember(member.name, [ - 'dimensions', - 'measures', - ]), - }; - }); + this.filters = [...allDimensions, ...this.measures].map((member) => ({ + ...member, + operators: this.meta.filterOperatorsForMember(member.name, [ + 'dimensions', + 'measures', + ]), + })); } } diff --git a/packages/cubejs-client-ngx/src/query-builder/chart-type.ts b/packages/cubejs-client-ngx/src/query-builder/chart-type.ts index 9d950086667ab..4342c27918c05 100644 --- a/packages/cubejs-client-ngx/src/query-builder/chart-type.ts +++ b/packages/cubejs-client-ngx/src/query-builder/chart-type.ts @@ -3,7 +3,7 @@ import { StateSubject } from './common'; export type TChartType = 'line' | 'area' | 'bar' | 'number' | 'table' | 'pie'; export class ChartType extends StateSubject { - constructor(value) { + public constructor(value) { super(value); } } diff --git a/packages/cubejs-client-ngx/src/query-builder/common.ts b/packages/cubejs-client-ngx/src/query-builder/common.ts index 13f11ebabdab0..26a658088b356 100644 --- a/packages/cubejs-client-ngx/src/query-builder/common.ts +++ b/packages/cubejs-client-ngx/src/query-builder/common.ts @@ -1,17 +1,17 @@ import { BehaviorSubject } from 'rxjs'; export class StateSubject { - subject: BehaviorSubject; + public subject: BehaviorSubject; - constructor(value: T) { + public constructor(value: T) { this.subject = new BehaviorSubject(value); } - get() { + public get() { return this.subject.getValue(); } - set(value: T) { + public set(value: T) { this.subject.next(value); } } diff --git a/packages/cubejs-client-ngx/src/query-builder/pivot-config.ts b/packages/cubejs-client-ngx/src/query-builder/pivot-config.ts index 4a453926212c0..863ca1ddd5a1f 100644 --- a/packages/cubejs-client-ngx/src/query-builder/pivot-config.ts +++ b/packages/cubejs-client-ngx/src/query-builder/pivot-config.ts @@ -6,11 +6,11 @@ import { import { StateSubject } from './common'; export class PivotConfig extends StateSubject { - constructor(pivotConfig: TPivotConfig) { + public constructor(pivotConfig: TPivotConfig) { super(pivotConfig); } - - moveItem( + + public moveItem( sourceIndex: number, destinationIndex: number, sourceAxis: TSourceAxis, @@ -26,8 +26,8 @@ export class PivotConfig extends StateSubject { ) ); } - - setFillMissingDates(fillMissingDates: boolean) { + + public setFillMissingDates(fillMissingDates: boolean) { this.subject.next({ ...this.get(), fillMissingDates diff --git a/packages/cubejs-client-ngx/src/query-builder/query-builder.service.ts b/packages/cubejs-client-ngx/src/query-builder/query-builder.service.ts index ba1f5e46fe7ad..31a8e36603aae 100644 --- a/packages/cubejs-client-ngx/src/query-builder/query-builder.service.ts +++ b/packages/cubejs-client-ngx/src/query-builder/query-builder.service.ts @@ -26,23 +26,32 @@ export type TQueryBuilderState = { @Injectable() export class QueryBuilderService { private _cube: CubeClient; + private _meta: Meta; + private _query: Query; + private _disableHeuristics: boolean = false; + private _resolveQuery: (query: Query) => void; + private _resolveBuilderMeta: (query: BuilderMeta) => void; + private _heuristicChange$ = new Subject(); - readonly builderMeta = new Promise( - (resolve) => (this._resolveBuilderMeta = resolve) - ); - readonly query = new Promise( - (resolve) => (this._resolveQuery = resolve) - ); - readonly state = new BehaviorSubject({}); + public readonly builderMeta = new Promise((resolve) => { + this._resolveBuilderMeta = resolve; + }); + + public readonly query = new Promise((resolve) => { + this._resolveQuery = resolve; + }); + + public readonly state = new BehaviorSubject({}); - pivotConfig: PivotConfig; - chartType: ChartType; + public pivotConfig: PivotConfig; + + public chartType: ChartType; private async init() { this.pivotConfig = new PivotConfig(null); @@ -64,24 +73,22 @@ export class QueryBuilderService { if (!this._disableHeuristics) { this._heuristicChange$ .pipe( - switchMap((data) => { - return combineLatest([ - this._cube.dryRun(data.query).pipe(catchError((error) => { - console.error(error); - return of(null); - })), - of(data.shouldApplyHeuristicOrder), - ]); - }) + switchMap((data) => combineLatest([ + this._cube.dryRun(data.query).pipe(catchError((error) => { + console.error(error); + return of(null); + })), + of(data.shouldApplyHeuristicOrder), + ])) ) .subscribe( ([dryRunResponse, shouldApplyHeuristicOrder]) => { if (!dryRunResponse) { return; } - + const { pivotQuery, queryOrder } = dryRunResponse; - + this.pivotConfig.set( ResultSet.getNormalizedPivotConfig( pivotQuery, @@ -126,7 +133,7 @@ export class QueryBuilderService { return query; } - setCubeClient(cubeClient: CubeClient) { + public setCubeClient(cubeClient: CubeClient) { this._cube = cubeClient; this.init(); } @@ -134,11 +141,9 @@ export class QueryBuilderService { private subscribe() { Object.getOwnPropertyNames(this).forEach((key) => { if (this[key] instanceof StateSubject) { - this[key].subject.subscribe((value) => - this.setPartialState({ - [key]: value, - }) - ); + this[key].subject.subscribe((value) => this.setPartialState({ + [key]: value, + })); } }); this.query.then((query) => { @@ -150,7 +155,7 @@ export class QueryBuilderService { }); } - async deserialize(state) { + public async deserialize(state) { if (state.query) { (await this.query).setQuery(state.query); } @@ -164,18 +169,18 @@ export class QueryBuilderService { this.subscribe(); } - setPartialState(partialState) { + public setPartialState(partialState) { this.state.next({ ...this.state.getValue(), ...partialState, }); } - disableHeuristics() { - this._disableHeuristics = false; + public disableHeuristics() { + this._disableHeuristics = true; } - enableHeuristics() { - this._disableHeuristics = true; + public enableHeuristics() { + this._disableHeuristics = false; } } diff --git a/packages/cubejs-client-ngx/src/query-builder/query-members.ts b/packages/cubejs-client-ngx/src/query-builder/query-members.ts index 69db485db9369..c4a21cd1a88d5 100644 --- a/packages/cubejs-client-ngx/src/query-builder/query-members.ts +++ b/packages/cubejs-client-ngx/src/query-builder/query-members.ts @@ -21,7 +21,7 @@ export type TOrderMember = { }; export class BaseMember { - constructor( + public constructor( private query: Query, private field: 'measures' | 'dimensions' | 'segments' ) {} @@ -30,59 +30,55 @@ export class BaseMember { return this.query.asCubeQuery()[this.field] || []; } - add(name: string) { + public add(name: string) { this.query.setPartialQuery({ [this.field]: [...this.members, name], }); } - replace(name: string, replaceWithName: string) { + public replace(name: string, replaceWithName: string) { this.query.setPartialQuery({ - [this.field]: this.members.map((currentName) => - currentName === name ? replaceWithName : currentName + [this.field]: this.members.map( + (currentName) => (currentName === name ? replaceWithName : currentName) ), }); } - remove(by: string | number) { + public remove(by: string | number) { this.query.setPartialQuery({ - [this.field]: this.query - .asCubeQuery() - [this.field].filter((currentName, index) => { - if (typeof by === 'string') { - return currentName !== by; - } + [this.field]: this.members.filter((currentName, index) => { + if (typeof by === 'string') { + return currentName !== by; + } - return index !== by; - }), + return index !== by; + }), }); } - set(members: string[]) { + public set(members: string[]) { this.query.setPartialQuery({ [this.field]: members, }); } - asArray() { - return (this.query.asCubeQuery()[this.field] || []).map((name) => - this.query.meta.resolveMember(name, this.field) - ); + public asArray() { + return this.members.map((name) => this.query.meta.resolveMember(name, this.field)); } } export class TimeDimensionMember { - constructor(private query: Query) {} + public constructor(private query: Query) {} private get members() { return this.query.asCubeQuery().timeDimensions || []; } - get granularity() { + public get granularity() { return this.members[0]?.granularity; } - updateTimeDimension(by: string | number, updateWith: any) { + public updateTimeDimension(by: string | number, updateWith: any) { const timeDimensions = this.members.map((td, index) => { if (td.dimension === by || index === by) { return { @@ -98,7 +94,7 @@ export class TimeDimensionMember { }); } - add(name: string) { + public add(name: string) { this.query.setPartialQuery({ timeDimensions: [ { @@ -108,7 +104,7 @@ export class TimeDimensionMember { }); } - remove(name: string) { + public remove(name: string) { this.query.setPartialQuery({ timeDimensions: this.members.filter( ({ dimension }) => dimension !== name @@ -116,43 +112,41 @@ export class TimeDimensionMember { }); } - set(timeDimensions: any[]) { + public set(timeDimensions: any[]) { this.query.setPartialQuery({ timeDimensions, }); } - setDateRange(by: string | number, dateRange: string | string[]) { + public setDateRange(by: string | number, dateRange: string | string[]) { this.updateTimeDimension(by, { dateRange }); } - setGranularity(by: string | number, granularity: TimeDimensionGranularity) { + public setGranularity(by: string | number, granularity: TimeDimensionGranularity) { this.updateTimeDimension(by, { granularity }); } - asArray(): any[] { - return (this.query.asCubeQuery().timeDimensions || []).map((td) => { - return { - ...this.query.meta.resolveMember(td.dimension, 'dimensions'), - ...td, - }; - }); + public asArray(): any[] { + return this.members.map((td) => ({ + ...this.query.meta.resolveMember(td.dimension, 'dimensions'), + ...td, + })); } } export class Order { - orderMembers = new BehaviorSubject([]); + public orderMembers = new BehaviorSubject([]); - constructor(private query: Query) { + public constructor(private query: Query) { this.query.subject.subscribe(this.handleQueryChange.bind(this)); this.orderMembers.subscribe(this.handleOrderMembersChange.bind(this)); } private handleOrderMembersChange(orderMembers: TOrderMember[]) { const order = orderMembers - .filter(({ order }) => order !== 'none') + .filter((orderMember) => orderMember.order !== 'none') .reduce( - (memo, { id, order }) => ({ ...memo, [id]: order }), + (memo, orderMember) => ({ ...memo, [orderMember.id]: orderMember.order }), {} ) as TQueryOrderObject; @@ -167,17 +161,15 @@ export class Order { ...this.query.measures.asArray(), ...this.query.dimensions.asArray(), ...this.query.timeDimensions.asArray(), - ].map(({ name, title }) => { - return { - id: name, - order: this.of(name), - title, - }; - }) + ].map(({ name, title }) => ({ + id: name, + order: this.of(name), + title, + })) ); } - setMemberOrder(id: string, order: TOrder) { + public setMemberOrder(id: string, order: TOrder) { this.orderMembers.next( this.orderMembers.getValue().map((orderMember) => { if (orderMember.id === id) { @@ -191,7 +183,7 @@ export class Order { ); } - reorder(sourceIndex: number, destinationIndex: number) { + public reorder(sourceIndex: number, destinationIndex: number) { this.orderMembers.next( moveItemInArray( this.orderMembers.getValue(), @@ -201,15 +193,15 @@ export class Order { ); } - of(member: string) { + public of(member: string) { return (this.query.asCubeQuery().order || {})[member] || 'none'; } - set(order: TQueryOrderObject | TQueryOrderArray) { + public set(order: TQueryOrderObject | TQueryOrderArray) { this.query.setPartialQuery({ order }); } - asArray(): TQueryOrderArray { + public asArray(): TQueryOrderArray { if (Array.isArray(this.query.asCubeQuery().order)) { return this.query.asCubeQuery().order as TQueryOrderArray; } @@ -217,7 +209,7 @@ export class Order { return Object.entries(this.query.asCubeQuery().order || {}); } - asObject(): TQueryOrderObject { + public asObject(): TQueryOrderObject { return this.asArray().reduce( (memo, [key, value]) => ({ ...memo, [key]: value }), {} @@ -226,14 +218,14 @@ export class Order { } export class FilterMember { - constructor(private query: Query) {} + public constructor(private query: Query) {} private get filters() { // TODO: update this type assertion once the QueryBuilder supports logical and/or return (this.query.asCubeQuery().filters || []) as (UnaryFilter | BinaryFilter)[]; } - update(by: string | number, updateWith: Partial) { + public update(by: string | number, updateWith: Partial) { const filters = this.filters.map((filter, index) => { if (index === by || filter.member === by || filter.dimension === by) { return { @@ -249,13 +241,13 @@ export class FilterMember { }); } - add(filter: Filter) { + public add(filter: Filter) { this.query.setPartialQuery({ filters: [...this.filters, filter], }); } - remove(by: string | number) { + public remove(by: string | number) { this.query.setPartialQuery({ filters: this.filters.filter((filter, index) => { if (filter.member === by || filter.dimension === by || index === by) { @@ -267,39 +259,37 @@ export class FilterMember { }); } - set(filters: Filter[]) { + public set(filters: Filter[]) { this.query.setPartialQuery({ filters, }); } - replace(name: string, replaceWithName: string) { + public replace(name: string, replaceWithName: string) { this.query.setPartialQuery({ filters: this.filters.map((filter) => { const field = filter.member ? 'member' : 'dimension'; return filter.member === name || filter.dimension === name ? { - ...filter, - [field]: replaceWithName, - } + ...filter, + [field]: replaceWithName, + } : filter; }), }); } - asArray(): any[] { - return this.filters.map((filter) => { - return { - ...this.query.meta.resolveMember(filter.member || filter.dimension, [ - 'dimensions', - 'measures', - ]), - operators: this.query.meta.filterOperatorsForMember( - filter.member || filter.dimension, - ['dimensions', 'measures'] - ), - ...filter, - }; - }); + public asArray(): any[] { + return this.filters.map((filter) => ({ + ...this.query.meta.resolveMember(filter.member || filter.dimension, [ + 'dimensions', + 'measures', + ]), + operators: this.query.meta.filterOperatorsForMember( + filter.member || filter.dimension, + ['dimensions', 'measures'] + ), + ...filter, + })); } } diff --git a/packages/cubejs-client-ngx/src/query-builder/query.ts b/packages/cubejs-client-ngx/src/query-builder/query.ts index 1e1c9f67cafc1..39e0cf66e8927 100644 --- a/packages/cubejs-client-ngx/src/query-builder/query.ts +++ b/packages/cubejs-client-ngx/src/query-builder/query.ts @@ -9,6 +9,8 @@ export enum MemberType { Segments = 'segments', TimeDimensions = 'timeDimensions', Filters = 'filters', + // an enum member is not a binding, so this does not shadow the imported `Order` + // eslint-disable-next-line no-shadow Order = 'order', } @@ -19,14 +21,19 @@ export type OnChangeCallback = ( ) => TCubeQuery; export class Query extends StateSubject { - measures: BaseMember; - dimensions: BaseMember; - segments: BaseMember; - timeDimensions: TimeDimensionMember; - filters: FilterMember; - order: Order; - - constructor( + public measures: BaseMember; + + public dimensions: BaseMember; + + public segments: BaseMember; + + public timeDimensions: TimeDimensionMember; + + public filters: FilterMember; + + public order: Order; + + public constructor( public meta: Meta, private _onBeforeChange: OnChangeCallback = (newQuery) => newQuery ) { @@ -43,15 +50,15 @@ export class Query extends StateSubject { this.order = new Order(this); } - asCubeQuery(): TCubeQuery { + public asCubeQuery(): TCubeQuery { return this.subject.getValue() || {}; } - setQuery(query: TCubeQuery) { + public setQuery(query: TCubeQuery) { this.subject.next(this._onBeforeChange(query, this.subject.getValue(), this)); } - setPartialQuery(partialQuery: Partial) { + public setPartialQuery(partialQuery: Partial) { this.subject.next( this._onBeforeChange( { @@ -64,11 +71,11 @@ export class Query extends StateSubject { ); } - setLimit(limit: number) { + public setLimit(limit: number) { this.setPartialQuery({ limit }); } - - isPresent(): boolean { + + public isPresent(): boolean { return isQueryPresent(this.asCubeQuery()); } } diff --git a/packages/cubejs-client-react/.eslintrc.js b/packages/cubejs-client-react/.eslintrc.js deleted file mode 100644 index eee476603c427..0000000000000 --- a/packages/cubejs-client-react/.eslintrc.js +++ /dev/null @@ -1,80 +0,0 @@ -module.exports = { - extends: 'airbnb', - plugins: ['react', 'jsx-a11y', 'import', '@typescript-eslint'], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { jsx: true }, - }, - settings: { - 'import/resolver': { - node: { extensions: ['.js', '.jsx', '.ts', '.tsx'] }, - }, - }, - rules: { - // Handled by the compiler - 'no-undef': 0, - // Base rules replaced by their TypeScript-aware versions - 'no-unused-vars': 0, - '@typescript-eslint/no-unused-vars': ['error', { args: 'none', ignoreRestSiblings: true }], - 'no-use-before-define': 0, - '@typescript-eslint/no-use-before-define': 'error', - // The base rules count overload signatures as redeclarations - 'no-redeclare': 0, - '@typescript-eslint/no-redeclare': 'error', - 'no-dupe-class-members': 0, - '@typescript-eslint/no-dupe-class-members': 'error', - 'no-shadow': 0, - '@typescript-eslint/no-shadow': 'error', - 'object-curly-newline': 0, - 'react/jsx-no-bind': 0, - 'react/jsx-first-prop-new-line': 0, - 'react/jsx-indent-props': 0, - 'react/jsx-filename-extension': 0, - 'react/react-in-jsx-scope': 0, // remove when import React is ready - 'import/no-unresolved': 0, - 'react/jsx-props-no-spreading': 0, - 'comma-dangle': 0, - 'no-console': 0, - 'no-plusplus': 0, - 'import/prefer-default-export': 0, - 'import/no-named-as-default': 0, - 'import/no-named-as-default-member': 0, - 'arrow-parens': 0, - 'react/jsx-no-undef': 0, - 'react/jsx-tag-spacing': 0, - 'react/prefer-stateless-function': 0, - 'react/forbid-prop-types': 0, - 'react/prop-types': 0, - 'import/extensions': 0, - quotes: ['warn', 'single'], - 'no-prototype-builtins': 0, - 'class-methods-use-this': 0, - 'no-param-reassign': 0, - 'no-mixed-operators': 0, - 'no-else-return': 0, - 'react/static-property-placement': 0, - 'react/destructuring-assignment': 0, - 'max-len': [ - 'error', - 120, - 2, - { - ignoreUrls: true, - // The published JSDoc is prose copied into the declarations - ignoreComments: true, - ignoreRegExpLiterals: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - }, - ], - 'no-trailing-spaces': ['error', { skipBlankLines: true }], - 'react/sort-comp': [ - 1, - { - order: ['static-variables', 'static-methods', 'lifecycle', 'everything-else', 'render'], - }, - ], - }, -}; diff --git a/packages/cubejs-client-react/.oxlintrc.json b/packages/cubejs-client-react/.oxlintrc.json new file mode 100644 index 0000000000000..1c4aca919c2b8 --- /dev/null +++ b/packages/cubejs-client-react/.oxlintrc.json @@ -0,0 +1,13 @@ +{ + "$schema": "../../node_modules/oxlint/configuration_schema.json", + "extends": [ + "../../.oxlintrc.json", + "../cubejs-linter/airbnb-react.json" + ], + "env": { + "node": true, + "browser": true, + "es6": true + }, + "ignorePatterns": ["dist/", "lib/", "coverage/", "**/*.d.ts"] +} diff --git a/packages/cubejs-client-react/package.json b/packages/cubejs-client-react/package.json index dd9c84c1f6222..013b017920227 100644 --- a/packages/cubejs-client-react/package.json +++ b/packages/cubejs-client-react/package.json @@ -16,9 +16,7 @@ "scripts": { "build": "tsc --build", "tsc": "tsc --build", - "watch": "tsc --build --watch", - "lint": "eslint src --ext .ts,.tsx", - "lint:fix": "eslint --fix src --ext .ts,.tsx" + "watch": "tsc --build --watch" }, "files": [ "src", @@ -34,15 +32,6 @@ "@babel/core": "^7.24.5", "@types/ramda": "^0.27.40", "@types/react": "^16.9.41", - "@typescript-eslint/eslint-plugin": "^8.46.0", - "@typescript-eslint/parser": "^8.46.0", - "eslint": "^8.57.1", - "eslint-config-airbnb": "^18.1.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-node": "^10.0.0", - "eslint-plugin-react": "^7.20.0", "typescript": "~6.0.3" }, "peerDependencies": { diff --git a/packages/cubejs-client-vue3/package.json b/packages/cubejs-client-vue3/package.json index 07f7fb028de1b..a0e721052affb 100644 --- a/packages/cubejs-client-vue3/package.json +++ b/packages/cubejs-client-vue3/package.json @@ -14,7 +14,6 @@ "Cube Dev, Inc." ], "scripts": { - "lint": "eslint src tests", "test": "jest", "unit": "npm run test:unit", "test:unit": "jest", @@ -35,7 +34,6 @@ "@babel/preset-env": "^7.24.5", "@vue/test-utils": "^2.4", "babel-jest": "^29", - "eslint": "^7.21.0", "jest": "^29", "jest-environment-jsdom": "^29", "vue": "^3.0" @@ -43,22 +41,6 @@ "peerDependencies": { "vue": "^3.0.0" }, - "eslintConfig": { - "root": true, - "env": { - "es2021": true, - "node": true, - "jest": true - }, - "extends": [ - "eslint:recommended" - ], - "rules": {}, - "parserOptions": { - "ecmaVersion": 2021, - "sourceType": "module" - } - }, "browserslist": [ "> 1%", "last 2 versions", diff --git a/packages/cubejs-client-vue3/src/QueryBuilder.js b/packages/cubejs-client-vue3/src/QueryBuilder.js index 26b19c04a97bf..e99941df21d93 100644 --- a/packages/cubejs-client-vue3/src/QueryBuilder.js +++ b/packages/cubejs-client-vue3/src/QueryBuilder.js @@ -21,52 +21,47 @@ const toOrderMember = (member) => ({ title: member.title, }); -const reduceOrderMembers = (array) => - array.reduce((acc, { id, order }) => (order !== 'none' ? [...acc, [id, order]] : acc), []); +const reduceOrderMembers = (array) => array.reduce( + (acc, { id, order }) => (order !== 'none' ? [...acc, [id, order]] : acc), + [] +); -const operators = [ 'and', 'or' ] +const operators = ['and', 'or']; -const validateFilters = (filters) => - filters.reduce((acc, raw) => { - if (raw.operator) { - return [...acc, raw]; - } - - const validBooleanFilter = operators.reduce((acc, operator) => { - const filters = raw[operator]; +const validateFilters = (filters) => filters.reduce((acc, raw) => { + if (raw.operator) { + return [...acc, raw]; + } - const booleanFilters = validateFilters(filters || []); + const validBooleanFilter = operators.reduce((booleanAcc, operator) => { + const booleanFilters = validateFilters(raw[operator] || []); - if (booleanFilters.length) { - return { ...acc, [operator]: booleanFilters }; - } + if (booleanFilters.length) { + return { ...booleanAcc, [operator]: booleanFilters }; + } - return acc; - }, {}); + return booleanAcc; + }, {}); - if (operators.some((operator) => validBooleanFilter[operator])) { - return [...acc, validBooleanFilter]; - } + if (operators.some((operator) => validBooleanFilter[operator])) { + return [...acc, validBooleanFilter]; + } - return acc; - }, []); + return acc; +}, []); const getDimensionOrMeasure = (meta, m) => { const memberName = m.member || m.dimension; return memberName && meta.resolveMember(memberName, ['dimensions', 'measures']); }; -const resolveMembers = (meta, arr) => - arr && - arr.map((e, index) => { - return { - ...e, - member: getDimensionOrMeasure(meta, e), - index, - and: resolveMembers(meta, e.and), - or: resolveMembers(meta, e.or), - }; - }); +const resolveMembers = (meta, arr) => arr && arr.map((e, index) => ({ + ...e, + member: getDimensionOrMeasure(meta, e), + index, + and: resolveMembers(meta, e.and), + or: resolveMembers(meta, e.or), +})); export default { components: { @@ -139,7 +134,6 @@ export default { segments, timeDimensions, validatedQuery, - isQueryPresent, availableSegments, availableTimeDimensions, availableDimensions, @@ -160,7 +154,7 @@ export default { builderProps = { query, validatedQuery, - isQueryPresent, + isQueryPresent: this.isQueryPresent, chartType, measures, dimensions, @@ -329,7 +323,7 @@ export default { }); if (validatedQuery.filters) { - validatedQuery.filters = validateFilters(validatedQuery.filters) + validatedQuery.filters = validateFilters(validatedQuery.filters); } // only set limit and offset if there are elements otherwise an invalid request with just limit/offset @@ -374,7 +368,7 @@ export default { }; this.chartType = chartType || this.chartType; - let pivot = ResultSet.getNormalizedPivotConfig( + const pivot = ResultSet.getNormalizedPivotConfig( validatedQuery, pivotConfig !== undefined ? pivotConfig : this.pivotConfig ); @@ -496,7 +490,7 @@ export default { and: resolveMembers(this.meta, member.and), or: resolveMembers(this.meta, member.or), member: getDimensionOrMeasure(this.meta, member), - } + }; } else { mem = this[`available${name}`].find((m) => m.name === member); } diff --git a/packages/cubejs-client-vue3/src/QueryRenderer.js b/packages/cubejs-client-vue3/src/QueryRenderer.js index ef38a6d54748a..821a44607d53e 100644 --- a/packages/cubejs-client-vue3/src/QueryRenderer.js +++ b/packages/cubejs-client-vue3/src/QueryRenderer.js @@ -129,14 +129,12 @@ export default { this.loading = true; const resultPromises = Promise.all( - toPairs(queries).map(([name, query]) => - this.cubeApi - .load(query, { - mutexObj: this.mutexObj, - mutexKey: name, - }) - .then((r) => [name, r]) - ) + toPairs(queries).map(([name, query]) => this.cubeApi + .load(query, { + mutexObj: this.mutexObj, + mutexKey: name, + }) + .then((r) => [name, r])) ); this.resultSet = fromPairs(await resultPromises); diff --git a/packages/cubejs-client-vue3/tests/unit/.eslintrc.js b/packages/cubejs-client-vue3/tests/unit/.eslintrc.js deleted file mode 100644 index 013a195bf0433..0000000000000 --- a/packages/cubejs-client-vue3/tests/unit/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - env: { - jest: true - } -} \ No newline at end of file diff --git a/packages/cubejs-client-vue3/tests/unit/QueryBuilder.spec.js b/packages/cubejs-client-vue3/tests/unit/QueryBuilder.spec.js index 5ddd5269ac686..b9041dd5544f8 100644 --- a/packages/cubejs-client-vue3/tests/unit/QueryBuilder.spec.js +++ b/packages/cubejs-client-vue3/tests/unit/QueryBuilder.spec.js @@ -505,9 +505,9 @@ describe('QueryBuilder.vue', () => { it('filters with boolean logical operators without explicit set', async () => { const cube = createCubeApi(); jest - .spyOn(cube, 'request') - .mockImplementation(fetchMock(load)) - .mockImplementationOnce(fetchMock(meta)); + .spyOn(cube, 'request') + .mockImplementation(fetchMock(load)) + .mockImplementationOnce(fetchMock(meta)); const filter = { or: [ @@ -872,9 +872,9 @@ describe('QueryBuilder.vue', () => { const cube = createCubeApi(); jest - .spyOn(cube, 'request') - .mockImplementation(fetchMock(load)) - .mockImplementationOnce(fetchMock(meta)); + .spyOn(cube, 'request') + .mockImplementation(fetchMock(load)) + .mockImplementationOnce(fetchMock(meta)); const wrapper = shallowMount(QueryBuilder, { propsData: { @@ -1028,9 +1028,9 @@ describe('QueryBuilder.vue', () => { it('does not contain time dimension if granularity is set to none', async () => { const cube = createCubeApi(); jest - .spyOn(cube, 'request') - .mockImplementation(fetchMock(load)) - .mockImplementationOnce(fetchMock(meta)); + .spyOn(cube, 'request') + .mockImplementation(fetchMock(load)) + .mockImplementationOnce(fetchMock(meta)); const wrapper = shallowMount(QueryBuilder, { props: { @@ -1048,22 +1048,22 @@ describe('QueryBuilder.vue', () => { expect(wrapper.vm.orderMembers.length).toBe(1); expect(wrapper.vm.orderMembers).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: 'Orders.count', - title: 'Orders Count', - order: 'none', - }), - ]) + expect.arrayContaining([ + expect.objectContaining({ + id: 'Orders.count', + title: 'Orders Count', + order: 'none', + }), + ]) ); }); it('contains time dimension if granularity is not none', async () => { const cube = createCubeApi(); jest - .spyOn(cube, 'request') - .mockImplementation(fetchMock(load)) - .mockImplementationOnce(fetchMock(meta)); + .spyOn(cube, 'request') + .mockImplementation(fetchMock(load)) + .mockImplementationOnce(fetchMock(meta)); const wrapper = shallowMount(QueryBuilder, { props: { @@ -1082,18 +1082,18 @@ describe('QueryBuilder.vue', () => { expect(wrapper.vm.orderMembers.length).toBe(2); expect(wrapper.vm.orderMembers).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: 'Orders.createdAt', - title: 'Orders Created at', - order: 'none' - }), - expect.objectContaining({ - id: 'Orders.count', - title: 'Orders Count', - order: 'none', - }) - ]) + expect.arrayContaining([ + expect.objectContaining({ + id: 'Orders.createdAt', + title: 'Orders Created at', + order: 'none' + }), + expect.objectContaining({ + id: 'Orders.count', + title: 'Orders Count', + order: 'none', + }) + ]) ); }); it('calls copyQueryFromProps if query is changed', async () => { diff --git a/packages/cubejs-client-vue3/tests/unit/__mocks__/fileMock.js b/packages/cubejs-client-vue3/tests/unit/__mocks__/fileMock.js index 850025b2b115d..9dc5fc1e4a43e 100644 --- a/packages/cubejs-client-vue3/tests/unit/__mocks__/fileMock.js +++ b/packages/cubejs-client-vue3/tests/unit/__mocks__/fileMock.js @@ -1 +1 @@ -module.exports = ''; \ No newline at end of file +module.exports = ''; diff --git a/packages/cubejs-client-vue3/tests/unit/__mocks__/responses.js b/packages/cubejs-client-vue3/tests/unit/__mocks__/responses.js index 58aeb40f3066d..dfbafd2e5308c 100644 --- a/packages/cubejs-client-vue3/tests/unit/__mocks__/responses.js +++ b/packages/cubejs-client-vue3/tests/unit/__mocks__/responses.js @@ -723,7 +723,7 @@ export default (body = {}, status = 200) => () => ({ status, json: async () => body, text: async () => JSON.stringify(body), - clone: function() { + clone() { return this; }, ok: status >= 200 && status <= 399 diff --git a/packages/cubejs-client-ws-transport/package.json b/packages/cubejs-client-ws-transport/package.json index 153a9e68f28e3..319e7fd90d5a1 100644 --- a/packages/cubejs-client-ws-transport/package.json +++ b/packages/cubejs-client-ws-transport/package.json @@ -14,9 +14,7 @@ "scripts": { "build": "npm run tsc", "tsc": "tsc && rm -rf temp", - "watch": "tsc -w", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "watch": "tsc -w" }, "dependencies": { "@babel/runtime": "^7.13.9", @@ -40,8 +38,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-crate-driver/package.json b/packages/cubejs-crate-driver/package.json index eb8eaa2ded2e3..f8a9d437c01d1 100644 --- a/packages/cubejs-crate-driver/package.json +++ b/packages/cubejs-crate-driver/package.json @@ -24,9 +24,7 @@ "test": "yarn integration", "integration": "npm run integration:crate", "integration:crate": "jest --verbose dist/test", - "unit": "jest --forceExit --verbose dist/test/CrateDriver.unit.test.js", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "unit": "jest --forceExit --verbose dist/test/CrateDriver.unit.test.js" }, "dependencies": { "@cubejs-backend/postgres-driver": "1.7.35", @@ -46,8 +44,5 @@ }, "jest": { "testEnvironment": "node" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-cubestore-driver/package.json b/packages/cubejs-cubestore-driver/package.json index 2fde2ab741c05..b919f6b7c5669 100644 --- a/packages/cubejs-cubestore-driver/package.json +++ b/packages/cubejs-cubestore-driver/package.json @@ -22,8 +22,6 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "lint": "eslint src/*.ts test/*.ts", - "lint:fix": "eslint --fix src/*.ts test/*.ts", "unit": "jest --coverage" }, "dependencies": { @@ -52,8 +50,5 @@ "license": "Apache-2.0", "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-databricks-jdbc-driver/package.json b/packages/cubejs-databricks-jdbc-driver/package.json index 41d3fba745738..4721f574692c6 100644 --- a/packages/cubejs-databricks-jdbc-driver/package.json +++ b/packages/cubejs-databricks-jdbc-driver/package.json @@ -20,8 +20,6 @@ "watch": "tsc -w", "test": "npm run unit-tests", "unit-tests": "NODE_OPTIONS=--experimental-vm-modules jest dist/test --forceExit", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts", "postinstall": "node bin/post-install" }, "files": [ @@ -49,8 +47,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts b/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts index 95a5f4b452099..63334ab110097 100644 --- a/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts +++ b/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts @@ -522,7 +522,7 @@ export class DatabricksDriver extends JDBCDriver { /** * Returns the list of the tables for the specified schema. */ - public async getTablesQuery(schemaName: string): Promise<{ 'table_name': string }[]> { + public async getTablesQuery(schemaName: string): Promise<{ table_name: string }[]> { const response = await this.query( `SHOW TABLES IN ${this.getSchemaFullName(schemaName)}`, [], @@ -701,7 +701,7 @@ export class DatabricksDriver extends JDBCDriver { const result = []; // eslint-disable-next-line camelcase - const response = await this.query<{col_name: string; data_type: string}>( + const response = await this.query<{ col_name: string; data_type: string }>( `DESCRIBE QUERY ${sql}`, params || [] ); diff --git a/packages/cubejs-databricks-jdbc-driver/src/DatabricksQuery.ts b/packages/cubejs-databricks-jdbc-driver/src/DatabricksQuery.ts index 2516c4a74c5ed..10034f57d0b79 100644 --- a/packages/cubejs-databricks-jdbc-driver/src/DatabricksQuery.ts +++ b/packages/cubejs-databricks-jdbc-driver/src/DatabricksQuery.ts @@ -221,7 +221,7 @@ export class DatabricksQuery extends BaseQuery { ' from_utc_timestamp({{ start }}, \'UTC\'), from_utc_timestamp({{ end }}, \'UTC\'), INTERVAL {{ granularity }}\n' + ' )) AS d)'; templates.statements.generated_time_series_with_cte_range_source = - 'SELECT d AS date_from,\n' + + 'SELECT d AS date_from,\n' + '(d + INTERVAL {{ granularity }}) - INTERVAL 1 MILLISECOND AS date_to\n' + 'FROM {{ range_source }}\n' + 'LATERAL VIEW explode(\n' + diff --git a/packages/cubejs-databricks-jdbc-driver/test/DatabricksDriver.test.ts b/packages/cubejs-databricks-jdbc-driver/test/DatabricksDriver.test.ts index a251e396d41e4..d6daef3cb1f62 100644 --- a/packages/cubejs-databricks-jdbc-driver/test/DatabricksDriver.test.ts +++ b/packages/cubejs-databricks-jdbc-driver/test/DatabricksDriver.test.ts @@ -25,7 +25,7 @@ jest.mock('@aws-sdk/s3-request-presigner', () => ({ })); jest.spyOn(ContainerClient.prototype, 'listBlobsFlat').mockImplementation( - jest.fn().mockReturnValue([{name: 'product.csv/test.csv'}]) + jest.fn().mockReturnValue([{ name: 'product.csv/test.csv' }]) ); jest.spyOn(BlobServiceClient.prototype, 'getUserDelegationKey').mockImplementation( jest.fn().mockReturnValue('mockKey') @@ -33,8 +33,8 @@ jest.spyOn(BlobServiceClient.prototype, 'getUserDelegationKey').mockImplementati describe('DatabricksDriver', () => { const mockTableName = 'product'; - const mockSql = 'SELECT * FROM ' + mockTableName; - const mockParams = [1] + const mockSql = `SELECT * FROM ${mockTableName}`; + const mockParams = [1]; const mockOptions: UnloadOptions = { maxFileSize: 3, query: { @@ -46,15 +46,15 @@ describe('DatabricksDriver', () => { const mockUnloadWithSql = jest.fn().mockResolvedValue('mockType'); beforeAll(() => { - process.env.CUBEJS_DB_DATABRICKS_ACCEPT_POLICY='true'; - process.env.CUBEJS_DB_DATABRICKS_URL='jdbc:databricks://adb-123456789.10.azuredatabricks.net:443'; - process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE='azure'; - process.env.CUBEJS_DB_EXPORT_BUCKET='wasbs://cube-export@mock.blob.core.windows.net'; - process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_KEY='azure-key'; - process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_TENANT_ID='azure-tenant-id'; - process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_CLIENT_ID='azure-client-id'; - process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_CLIENT_SECRET='azure-client-sceret' - process.env.CUBEJS_DB_DATABRICKS_TOKEN='token'; + process.env.CUBEJS_DB_DATABRICKS_ACCEPT_POLICY = 'true'; + process.env.CUBEJS_DB_DATABRICKS_URL = 'jdbc:databricks://adb-123456789.10.azuredatabricks.net:443'; + process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE = 'azure'; + process.env.CUBEJS_DB_EXPORT_BUCKET = 'wasbs://cube-export@mock.blob.core.windows.net'; + process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_KEY = 'azure-key'; + process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_TENANT_ID = 'azure-tenant-id'; + process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_CLIENT_ID = 'azure-client-id'; + process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_CLIENT_SECRET = 'azure-client-sceret'; + process.env.CUBEJS_DB_DATABRICKS_TOKEN = 'token'; }); afterEach(() => { @@ -71,7 +71,7 @@ describe('DatabricksDriver', () => { }); test('should get signed URLs of unloaded csv files by azure client secret', async () => { - process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_KEY=''; + process.env.CUBEJS_DB_EXPORT_BUCKET_AZURE_KEY = ''; databricksDriver = new DatabricksDriver(); databricksDriver['unloadWithSql'] = mockUnloadWithSql; diff --git a/packages/cubejs-dbt-schema-extension/package.json b/packages/cubejs-dbt-schema-extension/package.json index 283dc63c4c440..a045e3396cd2c 100644 --- a/packages/cubejs-dbt-schema-extension/package.json +++ b/packages/cubejs-dbt-schema-extension/package.json @@ -20,9 +20,7 @@ "scripts": { "build": "rm -rf dist && npm run tsc", "tsc": "tsc", - "watch": "tsc -w", - "lint": "eslint src/* --ext .ts,.js", - "lint:fix": "eslint --fix src/* --ext .ts,.js" + "watch": "tsc -w" }, "dependencies": { "@cubejs-backend/schema-compiler": "1.7.35", @@ -39,9 +37,6 @@ "stream-to-array": "^2.3.0", "typescript": "~6.0.3" }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cubejs-dremio-driver/package.json b/packages/cubejs-dremio-driver/package.json index 675ad61830b2b..b3cc374cb27cf 100644 --- a/packages/cubejs-dremio-driver/package.json +++ b/packages/cubejs-dremio-driver/package.json @@ -18,9 +18,7 @@ "test": "yarn integration", "unit": "jest --verbose dist/test/unit", "integration": "npm run integration:dremio", - "integration:dremio": "jest --verbose dist/test", - "lint": "eslint driver/*.js", - "lint:fix": "eslint driver/*.js" + "integration:dremio": "jest --verbose dist/test" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -38,8 +36,5 @@ "license": "Apache-2.0", "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-dremio-driver/test/DremioQuery.test.ts b/packages/cubejs-dremio-driver/test/DremioQuery.test.ts index 618ab45415d29..f9583d1777c75 100644 --- a/packages/cubejs-dremio-driver/test/DremioQuery.test.ts +++ b/packages/cubejs-dremio-driver/test/DremioQuery.test.ts @@ -8,7 +8,6 @@ const prepareCompiler = (content: string) => originalPrepareCompiler({ }); describe('DremioQuery', () => { - jest.setTimeout(10 * 60 * 1000); // Engine needs to spin up const { compiler, joinGraph, cubeEvaluator } = prepareCompiler( diff --git a/packages/cubejs-druid-driver/package.json b/packages/cubejs-druid-driver/package.json index e74b491a877d5..ff5d3904f04ce 100644 --- a/packages/cubejs-druid-driver/package.json +++ b/packages/cubejs-druid-driver/package.json @@ -19,9 +19,7 @@ "tsc": "tsc", "watch": "tsc -w", "integration": "jest dist/test/*.js", - "integration:druid": "jest dist/test/*.js", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:druid": "jest dist/test/*.js" }, "files": [ "README.md", @@ -43,8 +41,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-duckdb-driver/package.json b/packages/cubejs-duckdb-driver/package.json index 03926dcf5a7ad..08cb9668a6321 100644 --- a/packages/cubejs-duckdb-driver/package.json +++ b/packages/cubejs-duckdb-driver/package.json @@ -23,9 +23,7 @@ "watch": "tsc -w", "unit": "jest --verbose dist/test/unit", "integration": "npm run integration:duckdb", - "integration:duckdb": "jest --verbose dist/test", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:duckdb": "jest --verbose dist/test" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -44,8 +42,5 @@ }, "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-firebolt-driver/package.json b/packages/cubejs-firebolt-driver/package.json index 5258bc6e9441d..d64a1d8e5e5b7 100644 --- a/packages/cubejs-firebolt-driver/package.json +++ b/packages/cubejs-firebolt-driver/package.json @@ -23,9 +23,7 @@ "watch": "tsc -w", "test": "yarn integration", "integration": "npm run integration:firebolt", - "integration:firebolt": "jest --verbose dist/test --runInBand", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:firebolt": "jest --verbose dist/test --runInBand" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -49,8 +47,5 @@ "setupFiles": [ "./test/test-env.js" ] - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-firebolt-driver/src/FireboltDriver.ts b/packages/cubejs-firebolt-driver/src/FireboltDriver.ts index 649f724a658a7..34dfcfd1945c1 100644 --- a/packages/cubejs-firebolt-driver/src/FireboltDriver.ts +++ b/packages/cubejs-firebolt-driver/src/FireboltDriver.ts @@ -306,7 +306,7 @@ export class FireboltDriver extends BaseDriver implements DriverInterface { /* eslint-disable camelcase */ public async getTablesQuery(): Promise< { table_name?: string; TABLE_NAME?: string }[] - > { + > { const data = await this.query<{ table_name: string }>('SHOW TABLES', []); return data.map(({ table_name }) => ({ table_name })); } diff --git a/packages/cubejs-hive-driver/package.json b/packages/cubejs-hive-driver/package.json index cc2631e4671a1..e1e2e58407d20 100644 --- a/packages/cubejs-hive-driver/package.json +++ b/packages/cubejs-hive-driver/package.json @@ -12,10 +12,6 @@ "node": ">=20.0.0" }, "main": "src/HiveDriver.js", - "scripts": { - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" - }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", "@cubejs-backend/shared": "1.7.35", @@ -34,8 +30,5 @@ }, "jest": { "testEnvironment": "node" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-jdbc-driver/package.json b/packages/cubejs-jdbc-driver/package.json index e624207bcd9dc..9992f946cae1b 100644 --- a/packages/cubejs-jdbc-driver/package.json +++ b/packages/cubejs-jdbc-driver/package.json @@ -17,9 +17,7 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "unit": "jest --verbose dist/test/unit", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "unit": "jest --verbose dist/test/unit" }, "files": [ "dist/src", @@ -35,9 +33,6 @@ "java": "^0.18.0" }, "license": "Apache-2.0", - "eslintConfig": { - "extends": "../cubejs-linter" - }, "jest": { "testEnvironment": "node" }, diff --git a/packages/cubejs-jdbc-driver/src/JDBCDriver.ts b/packages/cubejs-jdbc-driver/src/JDBCDriver.ts index 3b4736e7c523a..c2a4e3ccf5224 100644 --- a/packages/cubejs-jdbc-driver/src/JDBCDriver.ts +++ b/packages/cubejs-jdbc-driver/src/JDBCDriver.ts @@ -252,7 +252,7 @@ export class JDBCDriver extends BaseDriver { public async query(query: string, values: unknown[]): Promise { const queryWithParams = this.prepareQueryWithParams(query, values); - const cancelObj: {cancel?: Function} = {}; + const cancelObj: { cancel?: Function } = {}; const promise = this.queryPromised(queryWithParams, cancelObj, this.prepareConnectionQueries()); (promise as CancelablePromise).cancel = () => cancelObj.cancel && cancelObj.cancel() || @@ -298,7 +298,7 @@ export class JDBCDriver extends BaseDriver { try { const query = this.prepareQueryWithParams(sql, values); - const cancelObj: {cancel?: Function} = {}; + const cancelObj: { cancel?: Function } = {}; const createStatement = promisify(conn.createStatement.bind(conn)); const statement = await createStatement(); @@ -314,10 +314,10 @@ export class JDBCDriver extends BaseDriver { ( err: unknown, res: { - labels: string[], - types: number[], - rows: { next: nextFn }, - }, + labels: string[], + types: number[], + rows: { next: nextFn }, + }, ) => { if (err) { reject(err); diff --git a/packages/cubejs-ksql-driver/package.json b/packages/cubejs-ksql-driver/package.json index 4b79d6120fcd0..0b96b84cdd8db 100644 --- a/packages/cubejs-ksql-driver/package.json +++ b/packages/cubejs-ksql-driver/package.json @@ -21,9 +21,7 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "unit": "jest --verbose dist/test/unit", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "unit": "jest --verbose dist/test/unit" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -37,9 +35,6 @@ "publishConfig": { "access": "public" }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "devDependencies": { "@cubejs-backend/linter": "1.7.35", "@types/jest": "^29", diff --git a/packages/cubejs-linter/.oxlintrc.json b/packages/cubejs-linter/.oxlintrc.json new file mode 100644 index 0000000000000..bb93a3bb38d8b --- /dev/null +++ b/packages/cubejs-linter/.oxlintrc.json @@ -0,0 +1,68 @@ +// Cube's shared oxlint configuration, extended by the repo-root .oxlintrc.json. +// +// airbnb-base.json is the mechanical port of the airbnb-base + typescript-eslint rule set +// this package used to export as an ESLint config. Everything in this file is a deliberate +// departure from that port, with the reason stated. +// +// Note that oxlint's `extends` merges `rules`, `plugins`, `jsPlugins` and `overrides`, but +// NOT `env` or `ignorePatterns` -- those are per-config-file and have to be restated by +// whoever extends this. An `overrides` entry's own `env` does carry across, though. +{ + "$schema": "../../node_modules/oxlint/configuration_schema.json", + "extends": ["./airbnb-base.json"], + "rules": { + // eslint-plugin-import never had a TypeScript resolver configured here + // (import/no-unresolved was off), so it could not resolve most imports and these five + // silently passed. oxlint resolves natively, which turns them on for the first time. + // Enabling them is separate work: as of this migration they report 173 dependency + // cycles, 366 hits on the `import R from 'ramda'; R.unnest()` idiom, and 2 genuine + // duplicate exports in client-core. + // + // import/named is off for a second reason: its verdict depends on build state, so it + // cannot gate anything. oxlint does not follow `export * from './time.js'` across a + // package boundary, so `import { GRANULARITIES } from '@cubejs-client/core'` reports + // "named import not found" once cubejs-client-core/dist exists and reports nothing + // before -- measured both ways on the same file. GRANULARITIES is genuinely exported + // (client-core src/time.ts:55, re-exported at src/index.ts:1026), so the warm-tree + // finding is a false positive; and CI is the cold case, since the lint job goes + // `yarn install` -> lint:npm -> lint:js with no build in between. + "import/no-cycle": "off", + "import/named": "off", + "import/no-named-as-default": "off", + "import/no-named-as-default-member": "off", + "import/export": "off", + // Two additions to what the ESLint config asked for, both about what `--fix` does to + // strings that contain quotes. `avoidEscape` is airbnb's default and was lost when this + // package downgraded `quotes` to a warning. `allowTemplateLiterals` matters more: the + // codebase uses backticks deliberately for SQL and JSON literals, and "fixing" those + // into single quotes turns e.g. the driver parameter-escaping tests into backslash + // soup -- which is precisely the code where quoting has to stay readable. + "@stylistic/quotes": [ + "warn", + "single", + { "avoidEscape": true, "allowTemplateLiterals": "always" } + ], + // oxlint's built-in fallthrough comment pattern is stricter than ESLint's default and + // rejected the existing `// falls through, trick from 90x ...` comments. + "no-fallthrough": ["error", { "commentPattern": "falls?\\s?through" }] + }, + "overrides": [ + { + // oxlint honours neither `/* globals ... */` nor `/* eslint-env jest */`, which is how + // the .js test suites used to declare the jest globals. + "files": [ + "**/test/**", + "**/tests/**", + "**/__mocks__/**", + "**/*.test.js", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.spec.ts", + "**/*.integration.js", + "**/*.integration.ts" + ], + "env": { "jest": true } + } + ] +} diff --git a/packages/cubejs-linter/airbnb-base.json b/packages/cubejs-linter/airbnb-base.json new file mode 100644 index 0000000000000..5adc01426d762 --- /dev/null +++ b/packages/cubejs-linter/airbnb-base.json @@ -0,0 +1,703 @@ +// GENERATED PORT -- do not hand-edit. This is the airbnb-base + typescript-eslint rule +// set that @cubejs-backend/linter used to export as an ESLint config, mapped 1:1 onto +// oxlint. Deliberate departures from it live in .oxlintrc.json, which extends this file. +// +// Rules with no oxlint or @stylistic equivalent, deliberately not carried over: +// +// camelcase, no-undef-init, lines-around-directive, strict +// no-restricted-syntax -- the ForInStatement ban; guard-for-in, no-labels and +// no-with (all still enabled) cover the rest of it +// consistent-return, dot-notation, no-return-await +// -- exist only as typescript/* rules needing options.typeAware +// import/order, import/no-extraneous-dependencies, import/no-useless-path-segments +// +// One rule is remapped rather than dropped: the ESLint config set `no-shadow: 'off'` plus +// `@typescript-eslint/no-shadow: ['error', { ignoreTypeValueShadow: true }]` in its *.ts +// override, and oxlint has no typescript/no-shadow -- configuring that key just routes to +// core no-shadow. Core no-shadow is used unqualified here because it already behaves the +// way ignoreTypeValueShadow asked for. A/B against eslint 8.57.1 + typescript-eslint +// 8.46.0 agrees on every shape tried: both ignore a type shadowing a value (`type Foo` + +// `const Foo`, a local `type X` or `` type parameter over an outer `const X`, a +// function-type parameter name over an outer value) and both report a type shadowing a +// type (`` or a local `interface T` over an outer `type T`) and an enum member +// shadowing an imported binding, at identical positions. +// no-dupe-args, no-octal, no-octal-escape +// -- parse errors under ESM/strict, so unreachable in practice +{ + "plugins": [ + "eslint", + "import", + "node", + "typescript", + "unicorn" + ], + "jsPlugins": [ + "@stylistic/eslint-plugin" + ], + "categories": { + "correctness": "off" + }, + "env": { + "node": true, + "es6": true + }, + "rules": { + "@stylistic/array-bracket-spacing": [ + "error", + "never" + ], + "@stylistic/arrow-spacing": [ + "error", + { + "before": true, + "after": true + } + ], + "@stylistic/block-spacing": [ + "error", + "always" + ], + "@stylistic/brace-style": [ + "error", + "1tbs", + { + "allowSingleLine": true + } + ], + "@stylistic/comma-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@stylistic/comma-style": [ + "error", + "last", + { + "exceptions": { + "ArrayExpression": false, + "ArrayPattern": false, + "ArrowFunctionExpression": false, + "CallExpression": false, + "FunctionDeclaration": false, + "FunctionExpression": false, + "ImportDeclaration": false, + "ObjectExpression": false, + "ObjectPattern": false, + "VariableDeclaration": false, + "NewExpression": false + } + } + ], + "@stylistic/computed-property-spacing": [ + "error", + "never" + ], + "@stylistic/dot-location": [ + "error", + "property" + ], + "@stylistic/eol-last": [ + "error", + "always" + ], + "@stylistic/function-call-spacing": [ + "error", + "never" + ], + "@stylistic/function-paren-newline": [ + "error", + "consistent" + ], + "@stylistic/generator-star-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@stylistic/implicit-arrow-linebreak": [ + "error", + "beside" + ], + "@stylistic/indent": [ + "error", + 2, + { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "FunctionDeclaration": { + "parameters": 1, + "body": 1 + }, + "FunctionExpression": { + "parameters": 1, + "body": 1 + }, + "CallExpression": { + "arguments": 1 + }, + "ArrayExpression": 1, + "ObjectExpression": 1, + "ImportDeclaration": 1, + "flatTernaryExpressions": false, + "ignoredNodes": [ + "JSXElement", + "JSXElement > *", + "JSXAttribute", + "JSXIdentifier", + "JSXNamespacedName", + "JSXMemberExpression", + "JSXSpreadAttribute", + "JSXExpressionContainer", + "JSXOpeningElement", + "JSXClosingElement", + "JSXFragment", + "JSXOpeningFragment", + "JSXClosingFragment", + "JSXText", + "JSXEmptyExpression", + "JSXSpreadChild" + ], + "ignoreComments": false, + "offsetTernaryExpressions": false + } + ], + "@stylistic/key-spacing": [ + "error", + { + "beforeColon": false, + "afterColon": true + } + ], + "@stylistic/keyword-spacing": [ + "error", + { + "before": true, + "after": true, + "overrides": { + "return": { + "after": true + }, + "throw": { + "after": true + }, + "case": { + "after": true + } + } + } + ], + "@stylistic/linebreak-style": [ + "error", + "unix" + ], + "@stylistic/lines-between-class-members": [ + "error", + "always", + { + "exceptAfterSingleLine": false + } + ], + "@stylistic/new-parens": "error", + "@stylistic/newline-per-chained-call": [ + "error", + { + "ignoreChainWithDepth": 4 + } + ], + "@stylistic/no-confusing-arrow": [ + "error", + { + "allowParens": true, + "onlyOneSimpleParam": false + } + ], + "@stylistic/no-extra-semi": "error", + "@stylistic/no-floating-decimal": "error", + "@stylistic/no-mixed-spaces-and-tabs": "error", + "@stylistic/no-multi-spaces": [ + "error", + { + "ignoreEOLComments": false + } + ], + "@stylistic/no-multiple-empty-lines": [ + "error", + { + "max": 1, + "maxBOF": 0, + "maxEOF": 0 + } + ], + "@stylistic/no-tabs": "error", + "@stylistic/no-trailing-spaces": [ + "warn", + { + "skipBlankLines": true, + "ignoreComments": false + } + ], + "@stylistic/no-whitespace-before-property": "error", + "@stylistic/nonblock-statement-body-position": [ + "error", + "beside", + { + "overrides": {} + } + ], + "@stylistic/object-curly-spacing": [ + "error", + "always" + ], + "@stylistic/object-property-newline": [ + "error", + { + "allowAllPropertiesOnSameLine": true + } + ], + "@stylistic/one-var-declaration-per-line": [ + "error", + "always" + ], + "@stylistic/padded-blocks": [ + "error", + { + "blocks": "never", + "classes": "never", + "switches": "never" + }, + { + "allowSingleLineBlocks": true + } + ], + "@stylistic/quote-props": [ + "error", + "as-needed", + { + "keywords": false, + "unnecessary": true, + "numbers": false + } + ], + "@stylistic/quotes": [ + "warn", + "single" + ], + "@stylistic/rest-spread-spacing": [ + "error", + "never" + ], + "@stylistic/semi": [ + "error", + "always" + ], + "@stylistic/semi-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "@stylistic/semi-style": [ + "error", + "last" + ], + "@stylistic/space-before-blocks": "error", + "@stylistic/space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ], + "@stylistic/space-in-parens": [ + "error", + "never" + ], + "@stylistic/space-infix-ops": "error", + "@stylistic/space-unary-ops": [ + "error", + { + "words": true, + "nonwords": false, + "overrides": {} + } + ], + "@stylistic/spaced-comment": [ + "error", + "always", + { + "line": { + "exceptions": [ + "-", + "+" + ], + "markers": [ + "=", + "!", + "/" + ] + }, + "block": { + "exceptions": [ + "-", + "+" + ], + "markers": [ + "=", + "!", + ":", + "::" + ], + "balanced": true + } + } + ], + "@stylistic/switch-colon-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "@stylistic/template-curly-spacing": "error", + "@stylistic/template-tag-spacing": [ + "error", + "never" + ], + "@stylistic/type-annotation-spacing": "error", + "@stylistic/wrap-iife": [ + "error", + "outside", + { + "functionPrototypeMethods": false + } + ], + "@stylistic/yield-star-spacing": [ + "error", + "after" + ], + "array-callback-return": [ + "error", + { + "allowImplicit": true, + "checkForEach": false, + "allowVoid": false + } + ], + "arrow-body-style": [ + "error", + "as-needed", + { + "requireReturnForObjectLiteral": false + } + ], + "block-scoped-var": "error", + "constructor-super": "error", + "curly": [ + "error", + "multi-line" + ], + "default-case": [ + "error", + { + "commentPattern": "^no default$" + } + ], + "eqeqeq": [ + "error", + "always", + { + "null": "ignore" + } + ], + "for-direction": "error", + "func-names": "warn", + "getter-return": [ + "error", + { + "allowImplicit": true + } + ], + "guard-for-in": "error", + "import/export": "error", + "import/first": "error", + "import/named": "error", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-amd": "error", + "import/no-cycle": [ + "error", + { + "ignoreExternal": false, + "allowUnsafeDynamicCyclicDependency": false, + "disableScc": false + } + ], + "import/no-duplicates": "error", + "import/no-dynamic-require": "error", + "import/no-mutable-exports": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-named-default": "error", + "import/no-self-import": "error", + "import/no-webpack-loader-syntax": "error", + "new-cap": [ + "error", + { + "newIsCap": true, + "newIsCapExceptions": [], + "capIsNew": false, + "capIsNewExceptions": [ + "Immutable.Map", + "Immutable.Set", + "Immutable.List" + ], + "properties": true + } + ], + "no-alert": "warn", + "no-array-constructor": "error", + "no-async-promise-executor": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": [ + "error", + "always" + ], + "no-const-assign": "error", + "no-constant-condition": "warn", + "no-continue": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-function": "error", + "no-empty-pattern": "error", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-boolean-cast": "error", + "no-extra-label": "error", + "no-fallthrough": "error", + "no-func-assign": "error", + "no-global-assign": [ + "error", + { + "exceptions": [] + } + ], + "no-implied-eval": "error", + "no-inner-declarations": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": [ + "error", + { + "allowLoop": false, + "allowSwitch": false + } + ], + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-misleading-character-class": "error", + "no-multi-assign": "error", + "no-multi-str": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-native-nonconstructor": "error", + "no-new-wrappers": "error", + "no-obj-calls": "error", + "no-object-constructor": "error", + "no-proto": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-restricted-globals": "error", + "no-restricted-properties": "error", + "no-return-assign": [ + "error", + "always" + ], + "no-script-url": "error", + "no-self-assign": [ + "error", + { + "props": true + } + ], + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-template-curly-in-string": "error", + "no-this-before-super": "error", + "no-throw-literal": "error", + "no-undef": "error", + "no-unexpected-multiline": "error", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": false + } + ], + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": false, + "allowTernary": false, + "allowTaggedTemplates": false, + "enforceForJSX": false + } + ], + "no-unused-labels": "error", + "no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_.*", + "varsIgnorePattern": "^_.*" + } + ], + "no-use-before-define": [ + "error", + { + "functions": true, + "classes": true, + "variables": true + } + ], + "no-useless-catch": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-escape": "error", + "no-useless-rename": [ + "error", + { + "ignoreDestructuring": false, + "ignoreImport": false, + "ignoreExport": false + } + ], + "no-useless-return": "error", + "no-var": "error", + "no-void": "error", + "no-with": "error", + "node/global-require": "error", + "node/no-new-require": "error", + "node/no-path-concat": "error", + "object-shorthand": [ + "error", + "always", + { + "ignoreConstructors": false, + "avoidQuotes": true + } + ], + "one-var": [ + "error", + "never" + ], + "operator-assignment": [ + "error", + "always" + ], + "prefer-arrow-callback": [ + "error", + { + "allowNamedFunctions": false, + "allowUnboundThis": true + } + ], + "prefer-const": [ + "error", + { + "destructuring": "any", + "ignoreReadBeforeAssign": true + } + ], + "prefer-destructuring": [ + "error", + { + "VariableDeclarator": { + "array": false, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": false + } + }, + { + "enforceForRenamedProperties": false + } + ], + "prefer-numeric-literals": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + "radix": "error", + "require-yield": "error", + "symbol-description": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/triple-slash-reference": "error", + "unicode-bom": [ + "error", + "never" + ], + "unicorn/no-new-buffer": "error", + "use-isnan": "error", + "valid-typeof": [ + "error", + { + "requireStringLiterals": true + } + ], + "vars-on-top": "error", + "yoda": "error" + }, + "overrides": [ + { + "files": [ + "**/*.ts", + "**/*.tsx" + ], + "rules": { + "constructor-super": "off", + "getter-return": "off", + "no-const-assign": "off", + "no-dupe-class-members": "off", + "no-dupe-keys": "off", + "no-func-assign": "off", + "no-new-native-nonconstructor": "off", + "no-obj-calls": "off", + "no-redeclare": "off", + "no-this-before-super": "off", + "no-undef": "off", + "no-unreachable": "off", + "no-unsafe-negation": "off", + "typescript/explicit-member-accessibility": "error", + "valid-typeof": "off" + } + } + ] +} diff --git a/packages/cubejs-linter/airbnb-react.json b/packages/cubejs-linter/airbnb-react.json new file mode 100644 index 0000000000000..bb00b2ba58673 --- /dev/null +++ b/packages/cubejs-linter/airbnb-react.json @@ -0,0 +1,917 @@ +// eslint-config-airbnb (the React flavour) ported to oxlint, expressed as the delta from +// the repo-root config. Extended by every React package in the repo: cubejs-client-react +// and cubejs-playground. +// +// Rules with no oxlint equivalent and not carried over: react/sort-comp, +// react/no-deprecated, react/no-typos, react/no-unused-state, +// react/no-access-state-in-setstate, and the PropTypes family (no-unused-prop-types, +// require-default-props, forbid-foreign-prop-types, default-props-match-prop-types) -- +// all legacy class-component rules, and both consumers are TS + hooks. +{ + "$schema": "../../node_modules/oxlint/configuration_schema.json", + "plugins": [ + "eslint", + "import", + "jsx-a11y", + "node", + "react", + "typescript", + "unicorn" + ], + "jsPlugins": [ + "@stylistic/eslint-plugin" + ], + "rules": { + "@stylistic/jsx-closing-bracket-location": [ + "error", + "line-aligned" + ], + "@stylistic/jsx-closing-tag-location": "error", + "@stylistic/jsx-curly-newline": [ + "error", + { + "multiline": "consistent", + "singleline": "consistent" + } + ], + "@stylistic/jsx-curly-spacing": [ + "error", + "never", + { + "allowMultiline": true + } + ], + "@stylistic/jsx-equals-spacing": [ + "error", + "never" + ], + "@stylistic/jsx-max-props-per-line": [ + "error", + { + "maximum": 1, + "when": "multiline" + } + ], + "@stylistic/jsx-one-expression-per-line": [ + "error", + { + "allow": "single-child" + } + ], + "@stylistic/jsx-quotes": [ + "error", + "prefer-double" + ], + "@stylistic/jsx-wrap-multilines": [ + "error", + { + "declaration": "parens-new-line", + "assignment": "parens-new-line", + "return": "parens-new-line", + "arrow": "parens-new-line", + "condition": "parens-new-line", + "logical": "parens-new-line", + "prop": "parens-new-line" + } + ], + "@stylistic/max-len": [ + "error", + 120, + 2, + { + "ignoreUrls": true, + "ignoreComments": true, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + "@stylistic/no-trailing-spaces": [ + "error", + { + "skipBlankLines": true, + "ignoreComments": false + } + ], + "@stylistic/operator-linebreak": [ + "error", + "before", + { + "overrides": { + "=": "none" + } + } + ], + "@stylistic/type-annotation-spacing": "off", + "import/no-named-as-default": "off", + "import/no-named-as-default-member": "off", + "jsx-a11y/alt-text": [ + "error", + { + "elements": [ + "img", + "object", + "area", + "input[type=\"image\"]" + ], + "img": [], + "object": [], + "area": [], + "input[type=\"image\"]": [] + } + ], + "jsx-a11y/anchor-has-content": [ + "error", + { + "components": [] + } + ], + "jsx-a11y/anchor-is-valid": [ + "error", + { + "components": [ + "Link" + ], + "specialLink": [ + "to" + ], + "aspects": [ + "noHref", + "invalidHref", + "preferButton" + ] + } + ], + "jsx-a11y/aria-activedescendant-has-tabindex": "error", + "jsx-a11y/aria-props": "error", + "jsx-a11y/aria-proptypes": "error", + "jsx-a11y/aria-role": [ + "error", + { + "ignoreNonDOM": false + } + ], + "jsx-a11y/aria-unsupported-elements": "error", + "jsx-a11y/click-events-have-key-events": "error", + "jsx-a11y/control-has-associated-label": [ + "error", + { + "labelAttributes": [ + "label" + ], + "controlComponents": [], + "ignoreElements": [ + "audio", + "canvas", + "embed", + "input", + "textarea", + "tr", + "video" + ], + "ignoreRoles": [ + "grid", + "listbox", + "menu", + "menubar", + "radiogroup", + "row", + "tablist", + "toolbar", + "tree", + "treegrid" + ], + "depth": 5 + } + ], + "jsx-a11y/heading-has-content": [ + "error", + { + "components": [ + "" + ] + } + ], + "jsx-a11y/html-has-lang": "error", + "jsx-a11y/iframe-has-title": "error", + "jsx-a11y/img-redundant-alt": "error", + "jsx-a11y/interactive-supports-focus": "error", + "jsx-a11y/label-has-associated-control": [ + "error", + { + "labelComponents": [], + "labelAttributes": [], + "controlComponents": [], + "assert": "both", + "depth": 25 + } + ], + "jsx-a11y/lang": "error", + "jsx-a11y/media-has-caption": [ + "error", + { + "audio": [], + "video": [], + "track": [] + } + ], + "jsx-a11y/mouse-events-have-key-events": "error", + "jsx-a11y/no-access-key": "error", + "jsx-a11y/no-autofocus": [ + "error", + { + "ignoreNonDOM": true + } + ], + "jsx-a11y/no-distracting-elements": [ + "error", + { + "elements": [ + "marquee", + "blink" + ] + } + ], + "jsx-a11y/no-interactive-element-to-noninteractive-role": [ + "error", + { + "tr": [ + "none", + "presentation" + ] + } + ], + "jsx-a11y/no-noninteractive-element-interactions": [ + "error", + { + "handlers": [ + "onClick", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ] + } + ], + "jsx-a11y/no-noninteractive-element-to-interactive-role": [ + "error", + { + "ul": [ + "listbox", + "menu", + "menubar", + "radiogroup", + "tablist", + "tree", + "treegrid" + ], + "ol": [ + "listbox", + "menu", + "menubar", + "radiogroup", + "tablist", + "tree", + "treegrid" + ], + "li": [ + "menuitem", + "option", + "row", + "tab", + "treeitem" + ], + "table": [ + "grid" + ], + "td": [ + "gridcell" + ] + } + ], + "jsx-a11y/no-noninteractive-tabindex": [ + "error", + { + "tags": [], + "roles": [ + "tabpanel" + ] + } + ], + "jsx-a11y/no-redundant-roles": "error", + "jsx-a11y/no-static-element-interactions": [ + "error", + { + "handlers": [ + "onClick", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ] + } + ], + "jsx-a11y/role-has-required-aria-props": "error", + "jsx-a11y/role-supports-aria-props": "error", + "jsx-a11y/scope": "error", + "jsx-a11y/tabindex-no-positive": "error", + "max-classes-per-file": [ + "error", + 1 + ], + "no-await-in-loop": "error", + "no-empty-function": [ + "error", + { + "allow": [ + "arrowFunctions", + "functions", + "methods" + ] + } + ], + "no-undef": "off", + "no-underscore-dangle": [ + "error", + { + "allow": [ + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__" + ], + "allowAfterThis": false, + "allowAfterSuper": false, + "enforceInMethodNames": true, + "allowAfterThisConstructor": false, + "allowFunctionParams": true, + "enforceInClassFields": false, + "allowInArrayDestructuring": true, + "allowInObjectDestructuring": true + } + ], + "no-unused-vars": [ + "error", + { + "args": "none", + "ignoreRestSiblings": true + } + ], + "no-use-before-define": "error", + "no-useless-constructor": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": [ + "error", + { + "allowEmptyReject": true + } + ], + "react/button-has-type": [ + "error", + { + "button": true, + "submit": true, + "reset": false + } + ], + "react/jsx-boolean-value": [ + "error", + "never", + { + "always": [] + } + ], + "react/jsx-curly-brace-presence": [ + "error", + { + "props": "never", + "children": "never" + } + ], + "react/jsx-fragments": [ + "error", + "syntax" + ], + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-duplicate-props": "error", + "react/jsx-no-target-blank": [ + "error", + { + "enforceDynamicLinks": "always", + "links": true, + "forms": false + } + ], + "react/jsx-pascal-case": [ + "error", + { + "allowAllCaps": true, + "ignore": [] + } + ], + "react/no-array-index-key": "error", + "react/no-children-prop": "error", + "react/no-danger": "warn", + "react/no-danger-with-children": "error", + "react/no-did-update-set-state": "error", + "react/no-find-dom-node": "error", + "react/no-is-mounted": "error", + "react/no-redundant-should-component-update": "error", + "react/no-render-return-value": "error", + "react/no-string-refs": "error", + "react/no-this-in-sfc": "error", + "react/no-unescaped-entities": "error", + "react/no-unknown-property": "error", + "react/no-will-update-set-state": "error", + "react/prefer-es6-class": [ + "error", + "always" + ], + "react/require-render-return": "error", + "react/self-closing-comp": "error", + "react/state-in-constructor": [ + "error", + "always" + ], + "react/style-prop-object": "error", + "react/void-dom-elements-no-children": "error", + "typescript/prefer-as-const": "off", + "typescript/prefer-namespace-keyword": "off", + "typescript/triple-slash-reference": "off", + "@stylistic/indent": [ + "error", + 2, + { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "FunctionDeclaration": { + "parameters": 1, + "body": 1 + }, + "FunctionExpression": { + "parameters": 1, + "body": 1 + }, + "CallExpression": { + "arguments": 1 + }, + "ArrayExpression": 1, + "ObjectExpression": 1, + "ImportDeclaration": 1, + "flatTernaryExpressions": false, + "ignoredNodes": [], + "ignoreComments": false, + "offsetTernaryExpressions": false + } + ] + }, + "overrides": [ + { + "files": [ + "**/*.ts", + "**/*.tsx" + ], + "rules": { + "@stylistic/jsx-closing-bracket-location": [ + "error", + "line-aligned" + ], + "@stylistic/jsx-closing-tag-location": "error", + "@stylistic/jsx-curly-newline": [ + "error", + { + "multiline": "consistent", + "singleline": "consistent" + } + ], + "@stylistic/jsx-curly-spacing": [ + "error", + "never", + { + "allowMultiline": true + } + ], + "@stylistic/jsx-equals-spacing": [ + "error", + "never" + ], + "@stylistic/jsx-max-props-per-line": [ + "error", + { + "maximum": 1, + "when": "multiline" + } + ], + "@stylistic/jsx-one-expression-per-line": [ + "error", + { + "allow": "single-child" + } + ], + "@stylistic/jsx-quotes": [ + "error", + "prefer-double" + ], + "@stylistic/jsx-wrap-multilines": [ + "error", + { + "declaration": "parens-new-line", + "assignment": "parens-new-line", + "return": "parens-new-line", + "arrow": "parens-new-line", + "condition": "parens-new-line", + "logical": "parens-new-line", + "prop": "parens-new-line" + } + ], + "@stylistic/max-len": [ + "error", + 120, + 2, + { + "ignoreUrls": true, + "ignoreComments": true, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + "@stylistic/no-trailing-spaces": [ + "error", + { + "skipBlankLines": true, + "ignoreComments": false + } + ], + "@stylistic/operator-linebreak": [ + "error", + "before", + { + "overrides": { + "=": "none" + } + } + ], + "@stylistic/type-annotation-spacing": "off", + "constructor-super": "error", + "getter-return": [ + "error", + { + "allowImplicit": true + } + ], + "import/no-named-as-default": "off", + "import/no-named-as-default-member": "off", + "jsx-a11y/alt-text": [ + "error", + { + "elements": [ + "img", + "object", + "area", + "input[type=\"image\"]" + ], + "img": [], + "object": [], + "area": [], + "input[type=\"image\"]": [] + } + ], + "jsx-a11y/anchor-has-content": [ + "error", + { + "components": [] + } + ], + "jsx-a11y/anchor-is-valid": [ + "error", + { + "components": [ + "Link" + ], + "specialLink": [ + "to" + ], + "aspects": [ + "noHref", + "invalidHref", + "preferButton" + ] + } + ], + "jsx-a11y/aria-activedescendant-has-tabindex": "error", + "jsx-a11y/aria-props": "error", + "jsx-a11y/aria-proptypes": "error", + "jsx-a11y/aria-role": [ + "error", + { + "ignoreNonDOM": false + } + ], + "jsx-a11y/aria-unsupported-elements": "error", + "jsx-a11y/click-events-have-key-events": "error", + "jsx-a11y/control-has-associated-label": [ + "error", + { + "labelAttributes": [ + "label" + ], + "controlComponents": [], + "ignoreElements": [ + "audio", + "canvas", + "embed", + "input", + "textarea", + "tr", + "video" + ], + "ignoreRoles": [ + "grid", + "listbox", + "menu", + "menubar", + "radiogroup", + "row", + "tablist", + "toolbar", + "tree", + "treegrid" + ], + "depth": 5 + } + ], + "jsx-a11y/heading-has-content": [ + "error", + { + "components": [ + "" + ] + } + ], + "jsx-a11y/html-has-lang": "error", + "jsx-a11y/iframe-has-title": "error", + "jsx-a11y/img-redundant-alt": "error", + "jsx-a11y/interactive-supports-focus": "error", + "jsx-a11y/label-has-associated-control": [ + "error", + { + "labelComponents": [], + "labelAttributes": [], + "controlComponents": [], + "assert": "both", + "depth": 25 + } + ], + "jsx-a11y/lang": "error", + "jsx-a11y/media-has-caption": [ + "error", + { + "audio": [], + "video": [], + "track": [] + } + ], + "jsx-a11y/mouse-events-have-key-events": "error", + "jsx-a11y/no-access-key": "error", + "jsx-a11y/no-autofocus": [ + "error", + { + "ignoreNonDOM": true + } + ], + "jsx-a11y/no-distracting-elements": [ + "error", + { + "elements": [ + "marquee", + "blink" + ] + } + ], + "jsx-a11y/no-interactive-element-to-noninteractive-role": [ + "error", + { + "tr": [ + "none", + "presentation" + ] + } + ], + "jsx-a11y/no-noninteractive-element-interactions": [ + "error", + { + "handlers": [ + "onClick", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ] + } + ], + "jsx-a11y/no-noninteractive-element-to-interactive-role": [ + "error", + { + "ul": [ + "listbox", + "menu", + "menubar", + "radiogroup", + "tablist", + "tree", + "treegrid" + ], + "ol": [ + "listbox", + "menu", + "menubar", + "radiogroup", + "tablist", + "tree", + "treegrid" + ], + "li": [ + "menuitem", + "option", + "row", + "tab", + "treeitem" + ], + "table": [ + "grid" + ], + "td": [ + "gridcell" + ] + } + ], + "jsx-a11y/no-noninteractive-tabindex": [ + "error", + { + "tags": [], + "roles": [ + "tabpanel" + ] + } + ], + "jsx-a11y/no-redundant-roles": "error", + "jsx-a11y/no-static-element-interactions": [ + "error", + { + "handlers": [ + "onClick", + "onMouseDown", + "onMouseUp", + "onKeyPress", + "onKeyDown", + "onKeyUp" + ] + } + ], + "jsx-a11y/role-has-required-aria-props": "error", + "jsx-a11y/role-supports-aria-props": "error", + "jsx-a11y/scope": "error", + "jsx-a11y/tabindex-no-positive": "error", + "max-classes-per-file": [ + "error", + 1 + ], + "no-await-in-loop": "error", + "no-const-assign": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-empty-function": [ + "error", + { + "allow": [ + "arrowFunctions", + "functions", + "methods" + ] + } + ], + "no-func-assign": "error", + "no-new-native-nonconstructor": "error", + "no-obj-calls": "error", + "no-redeclare": "error", + "no-this-before-super": "error", + "no-undef": "off", + "no-underscore-dangle": [ + "error", + { + "allow": [ + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__" + ], + "allowAfterThis": false, + "allowAfterSuper": false, + "enforceInMethodNames": true, + "allowAfterThisConstructor": false, + "allowFunctionParams": true, + "enforceInClassFields": false, + "allowInArrayDestructuring": true, + "allowInObjectDestructuring": true + } + ], + "no-unreachable": "error", + "no-unsafe-negation": "error", + "no-unused-vars": [ + "error", + { + "args": "none", + "ignoreRestSiblings": true + } + ], + "no-use-before-define": "error", + "no-useless-constructor": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": [ + "error", + { + "allowEmptyReject": true + } + ], + "react/button-has-type": [ + "error", + { + "button": true, + "submit": true, + "reset": false + } + ], + "react/jsx-boolean-value": [ + "error", + "never", + { + "always": [] + } + ], + "react/jsx-curly-brace-presence": [ + "error", + { + "props": "never", + "children": "never" + } + ], + "react/jsx-fragments": [ + "error", + "syntax" + ], + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-duplicate-props": "error", + "react/jsx-no-target-blank": [ + "error", + { + "enforceDynamicLinks": "always", + "links": true, + "forms": false + } + ], + "react/jsx-pascal-case": [ + "error", + { + "allowAllCaps": true, + "ignore": [] + } + ], + "react/no-array-index-key": "error", + "react/no-children-prop": "error", + "react/no-danger": "warn", + "react/no-danger-with-children": "error", + "react/no-did-update-set-state": "error", + "react/no-find-dom-node": "error", + "react/no-is-mounted": "error", + "react/no-redundant-should-component-update": "error", + "react/no-render-return-value": "error", + "react/no-string-refs": "error", + "react/no-this-in-sfc": "error", + "react/no-unescaped-entities": "error", + "react/no-unknown-property": "error", + "react/no-will-update-set-state": "error", + "react/prefer-es6-class": [ + "error", + "always" + ], + "react/require-render-return": "error", + "react/self-closing-comp": "error", + "react/state-in-constructor": [ + "error", + "always" + ], + "react/style-prop-object": "error", + "react/void-dom-elements-no-children": "error", + "typescript/explicit-member-accessibility": "off", + "typescript/prefer-as-const": "off", + "typescript/prefer-namespace-keyword": "off", + "typescript/triple-slash-reference": "off", + "valid-typeof": [ + "error", + { + "requireStringLiterals": true + } + ] + } + } + ] +} diff --git a/packages/cubejs-linter/index.js b/packages/cubejs-linter/index.js deleted file mode 100644 index 2e83c11f64c56..0000000000000 --- a/packages/cubejs-linter/index.js +++ /dev/null @@ -1,114 +0,0 @@ -module.exports = { - root: true, - extends: 'airbnb-base', - env: { - node: true, - }, - plugins: ['import', '@typescript-eslint/eslint-plugin', '@stylistic/ts'], - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - ecmaFeatures: { - legacyDecorators: true, - }, - }, - rules: { - 'no-useless-constructor': 0, - 'max-classes-per-file': 0, - 'prefer-object-spread': 0, - 'import/no-unresolved': 0, - 'comma-dangle': 0, - 'no-console': 0, - 'arrow-parens': 0, - 'import/prefer-default-export': 0, - 'import/extensions': 0, - quotes: ['warn', 'single'], - 'no-prototype-builtins': 0, - 'class-methods-use-this': 0, - 'no-param-reassign': 0, - 'no-mixed-operators': 0, - 'no-else-return': 0, - 'prefer-promise-reject-errors': 0, - 'no-plusplus': 0, - 'no-await-in-loop': 0, - 'operator-linebreak': 0, - // linter can't fix this itself and, in some cases, conflicts with `arrow-body-style` - 'max-len': 0, - 'no-trailing-spaces': ['warn', { skipBlankLines: true }], - 'object-curly-newline': 0, - // TypeScript Recommended - 'no-array-constructor': 'off', - '@typescript-eslint/no-array-constructor': 'error', - 'no-empty-function': 'off', - '@typescript-eslint/no-empty-function': 'error', - 'no-extra-semi': 'off', - '@stylistic/ts/no-extra-semi': 'error', - 'no-underscore-dangle': 'off', - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': [ - 'warn', - { - argsIgnorePattern: '^_.*', - varsIgnorePattern: '^_.*', - }, - ], - // '@typescript-eslint/no-var-requires': 'error', - '@typescript-eslint/prefer-as-const': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', - '@typescript-eslint/triple-slash-reference': 'error', - '@stylistic/ts/type-annotation-spacing': 'error', - '@stylistic/ts/space-infix-ops': 'error', - 'no-restricted-syntax': [ - 'error', - { - selector: 'ForInStatement', - message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.', - }, - { - selector: 'LabeledStatement', - message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', - }, - { - selector: 'WithStatement', - message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', - }, - ], - }, - overrides: [ - { - files: ['*.ts', '*.tsx'], - rules: { - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/eslint-recommended.ts - 'constructor-super': 'off', // ts(2335) & ts(2377) - 'getter-return': 'off', // ts(2378) - 'no-const-assign': 'off', // ts(2588) - 'no-dupe-args': 'off', // ts(2300) - 'no-dupe-class-members': 'off', // ts(2393) & ts(2300) - 'no-dupe-keys': 'off', // ts(1117) - 'no-func-assign': 'off', // ts(2539) - 'no-import-assign': 'off', // ts(2539) & ts(2540) - 'no-new-symbol': 'off', // ts(2588) - 'no-obj-calls': 'off', // ts(2349) - 'no-redeclare': 'off', // ts(2451) - 'no-setter-return': 'off', // ts(2408) - 'no-this-before-super': 'off', // ts(2376) - 'no-undef': 'off', // ts(2304) - 'no-unreachable': 'off', // ts(7027) - 'no-unsafe-negation': 'off', // ts(2365) & ts(2360) & ts(2358) - 'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more - 'prefer-const': 'error', // ts provides better types with const - 'prefer-rest-params': 'error', // ts provides better types with rest args over arguments - 'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply - 'valid-typeof': 'off', // ts(2367) - // TypeScript Additional - '@typescript-eslint/explicit-member-accessibility': 'error', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': ['error', { ignoreTypeValueShadow: true }], - 'no-duplicate-imports': 'off', - semi: 'off', - '@stylistic/ts/semi': 'error', - }, - }, - ], -}; diff --git a/packages/cubejs-linter/package.json b/packages/cubejs-linter/package.json index 3d2955f8b9481..6e2293440c71d 100644 --- a/packages/cubejs-linter/package.json +++ b/packages/cubejs-linter/package.json @@ -1,31 +1,24 @@ { "name": "@cubejs-backend/linter", - "description": "Cube.js ESLint (virtual package) for linting code", + "description": "Cube.js shared oxlint configuration (virtual package) for linting code", "author": "Cube Dev, Inc.", "version": "1.7.35", "repository": { "type": "git", "url": "https://github.com/cube-js/cube.git", - "directory": "packages/cubejs-mssql-driver" + "directory": "packages/cubejs-linter" }, "engines": { "node": ">=20.0.0" }, - "main": "index.js", - "peerDependencies": { - "eslint": ">=8.57" - }, "dependencies": { - "@stylistic/eslint-plugin-ts": "^3.1.0", - "@typescript-eslint/eslint-plugin": "^8.46.0", - "@typescript-eslint/parser": "^8.46.0", - "eslint": "^8.57.1", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^10.0.0" + "@stylistic/eslint-plugin": "^5.10.0", + "oxlint": "^1.82.0" }, "files": [ - "index.js" + ".oxlintrc.json", + "airbnb-base.json", + "airbnb-react.json" ], "license": "Apache-2.0", "publishConfig": { diff --git a/packages/cubejs-materialize-driver/package.json b/packages/cubejs-materialize-driver/package.json index feaade5571fce..e92f8bae502a3 100644 --- a/packages/cubejs-materialize-driver/package.json +++ b/packages/cubejs-materialize-driver/package.json @@ -22,9 +22,7 @@ "tsc": "tsc", "watch": "tsc -w", "integration": "npm run integration:materialize", - "integration:materialize": "jest --verbose dist/test", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:materialize": "jest --verbose dist/test" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -45,8 +43,5 @@ }, "jest": { "testEnvironment": "node" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-materialize-driver/src/MaterializeDriver.ts b/packages/cubejs-materialize-driver/src/MaterializeDriver.ts index bbd0db1554f4b..d843bbeb3285d 100644 --- a/packages/cubejs-materialize-driver/src/MaterializeDriver.ts +++ b/packages/cubejs-materialize-driver/src/MaterializeDriver.ts @@ -167,7 +167,7 @@ export class MaterializeDriver extends PostgresDriver { * @returns {Promise} version */ public async getMaterializeVersion(): Promise { - const [{ version }] = await this.query<{version: string}>('SELECT mz_version() as version;', []); + const [{ version }] = await this.query<{ version: string }>('SELECT mz_version() as version;', []); // Materialize returns the version as follows: 'v0.24.3-alpha.5 (65778f520)' return version.split(' ')[0]; diff --git a/packages/cubejs-materialize-driver/test/MaterializeDriver.test.ts b/packages/cubejs-materialize-driver/test/MaterializeDriver.test.ts index 2480b8a15409e..eebeaa5fef501 100644 --- a/packages/cubejs-materialize-driver/test/MaterializeDriver.test.ts +++ b/packages/cubejs-materialize-driver/test/MaterializeDriver.test.ts @@ -156,8 +156,7 @@ describe('MaterializeDriver', () => { const data = await driver.query(`SHOW CLUSTER;`, []); expect(data).toEqual([ { - 'cluster': 'quickstart', + cluster: 'quickstart', }]); }); - }); diff --git a/packages/cubejs-mongobi-driver/package.json b/packages/cubejs-mongobi-driver/package.json index 670f0fff474f0..fce5ce0e0e596 100644 --- a/packages/cubejs-mongobi-driver/package.json +++ b/packages/cubejs-mongobi-driver/package.json @@ -21,8 +21,6 @@ "build": "rm -rf dist && npm run tsc", "tsc": "tsc", "watch": "tsc -w", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts", "integration": "jest dist/test", "integration:mongobi": "jest dist/test" }, @@ -45,8 +43,5 @@ }, "jest": { "testEnvironment": "node" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-mssql-driver/package.json b/packages/cubejs-mssql-driver/package.json index 361eff9800bad..cfc5853863dbc 100644 --- a/packages/cubejs-mssql-driver/package.json +++ b/packages/cubejs-mssql-driver/package.json @@ -20,9 +20,7 @@ "scripts": { "build": "rm -rf dist && npm run tsc", "tsc": "tsc", - "watch": "tsc -w", - "lint": "eslint src/* --ext .ts,.js", - "lint:fix": "eslint --fix src/* --ext .ts,.js" + "watch": "tsc -w" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -36,9 +34,6 @@ "jest": { "testEnvironment": "node" }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cubejs-mssql-driver/src/MSSqlDriver.ts b/packages/cubejs-mssql-driver/src/MSSqlDriver.ts index d24fdc498e4c9..6ded1beebb0df 100644 --- a/packages/cubejs-mssql-driver/src/MSSqlDriver.ts +++ b/packages/cubejs-mssql-driver/src/MSSqlDriver.ts @@ -80,33 +80,33 @@ export class MSSqlDriver extends BaseDriver implements DriverInterface { * Class constructor. */ public constructor(config: MSSqlDriverConfiguration & { - /** + /** * Data source name. */ - dataSource?: string, + dataSource?: string, - /** + /** * Whether this driver is used for pre-aggregations. */ - preAggregations?: boolean, + preAggregations?: boolean, - /** + /** * Max pool size value for the [cube]<-->[db] pool. */ - maxPoolSize?: number, + maxPoolSize?: number, - /** + /** * Min pool size value for the [cube]<-->[db] pool. */ - minPoolSize?: number, + minPoolSize?: number, - /** + /** * Time to wait for a response from a connection after validation * request before determining it as not valid. Default - 10000 ms. */ - testConnectionTimeout?: number, - server?: string, - } = {}) { + testConnectionTimeout?: number, + server?: string, + } = {}) { super({ testConnectionTimeout: config.testConnectionTimeout, }); @@ -395,7 +395,7 @@ export class MSSqlDriver extends BaseDriver implements DriverInterface { return !!this.config.readOnly; } - public wrapQueryWithLimit(query: { query: string, limit: number}) { + public wrapQueryWithLimit(query: { query: string, limit: number }) { query.query = `SELECT TOP ${query.limit} * FROM (${query.query}) AS t`; } diff --git a/packages/cubejs-mysql-aurora-serverless-driver/package.json b/packages/cubejs-mysql-aurora-serverless-driver/package.json index fae0c947c3c68..5aee782acef0e 100644 --- a/packages/cubejs-mysql-aurora-serverless-driver/package.json +++ b/packages/cubejs-mysql-aurora-serverless-driver/package.json @@ -17,8 +17,7 @@ "test": "npm run unit && npm run integration", "unit": "jest ./**/*.test.js", "integration": "jest ./**/*.integration.js", - "integration:mysql-aurora-serverless": "npm run integration", - "lint": "eslint driver/*.js test/*.js" + "integration:mysql-aurora-serverless": "npm run integration" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -37,8 +36,5 @@ "license": "Apache-2.0", "publishConfig": { "access": "public" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-mysql-driver/package.json b/packages/cubejs-mysql-driver/package.json index 1e4ce877c7ea4..0f5568eca0845 100644 --- a/packages/cubejs-mysql-driver/package.json +++ b/packages/cubejs-mysql-driver/package.json @@ -22,9 +22,7 @@ "tsc": "tsc", "watch": "tsc -w", "integration": "npm run integration:mysql", - "integration:mysql": "jest --verbose dist/test", - "lint": "eslint src/* test/* --ext .ts,.js", - "lint:fix": "eslint --fix src/* test/* --ext .ts,.js" + "integration:mysql": "jest --verbose dist/test" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -41,9 +39,6 @@ "testcontainers": "^10.28.0", "typescript": "~6.0.3" }, - "eslintConfig": { - "extends": "../cubejs-linter" - }, "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cubejs-oracle-driver/driver/OracleDriver.js b/packages/cubejs-oracle-driver/driver/OracleDriver.js index c2796a12fc538..2312fc799c764 100644 --- a/packages/cubejs-oracle-driver/driver/OracleDriver.js +++ b/packages/cubejs-oracle-driver/driver/OracleDriver.js @@ -26,7 +26,7 @@ const OracleTypeToGenericType = { binary_float: 'float', binary_double: 'double', date: 'timestamp', - 'number': 'decimal', + number: 'decimal', }; const sortByKeys = (unordered) => { @@ -40,12 +40,12 @@ const sortByKeys = (unordered) => { }; const reduceCb = (result, i) => { - let schema = (result[i.table_schema] || {}); - let tables = (schema[i.table_name] || []); - let attributes = new Array(); + const schema = (result[i.table_schema] || {}); + const tables = (schema[i.table_name] || []); + const attributes = []; - if (i.key_type === "P" || i.key_type === "U") { - attributes.push(["primaryKey"]); + if (i.key_type === 'P' || i.key_type === 'U') { + attributes.push(['primaryKey']); } tables.push({ diff --git a/packages/cubejs-pinot-driver/package.json b/packages/cubejs-pinot-driver/package.json index 38e7c23243585..b8aa0ccda7a5e 100644 --- a/packages/cubejs-pinot-driver/package.json +++ b/packages/cubejs-pinot-driver/package.json @@ -23,9 +23,7 @@ "watch": "tsc -w", "unit": "jest --verbose dist/test/unit", "integration": "npm run integration:pinot", - "integration:pinot": "jest --verbose dist/test", - "lint": "eslint src/* --ext .ts", - "lint:fix": "eslint --fix src/* --ext .ts" + "integration:pinot": "jest --verbose dist/test" }, "dependencies": { "@cubejs-backend/base-driver": "1.7.35", @@ -46,8 +44,5 @@ "should": "^13.2.3", "testcontainers": "^10.28.0", "typescript": "~6.0.3" - }, - "eslintConfig": { - "extends": "../cubejs-linter" } } diff --git a/packages/cubejs-playground/.eslintignore b/packages/cubejs-playground/.eslintignore deleted file mode 100644 index 72e8ffc0db8aa..0000000000000 --- a/packages/cubejs-playground/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/packages/cubejs-playground/.oxlintrc.json b/packages/cubejs-playground/.oxlintrc.json new file mode 100644 index 0000000000000..6f3299405c528 --- /dev/null +++ b/packages/cubejs-playground/.oxlintrc.json @@ -0,0 +1,62 @@ +{ + "$schema": "../../node_modules/oxlint/configuration_schema.json", + "extends": [ + "../../.oxlintrc.json", + "../cubejs-linter/airbnb-react.json" + ], + "env": { + "node": true, + "browser": true, + "es6": true + }, + "ignorePatterns": [ + "build/", + "coverage/", + "lib/", + "public/", + "**/*.d.ts", + // a separate sub-project with its own package.json, yarn.lock and ESLint setup + "vizard/" + ], + "rules": { + // airbnb's `destructuring: "any"` reports a `let { ... } = props` pattern when any one + // binding is never reassigned, which is unfixable here: this package follows the + // @cube-dev/ui-kit idiom of reassigning some props-derived locals + // (`titleStyles = useMemo(...)`), so the pattern needs `let` and the rule reported 44 + // bindings no autofix could touch. "all" still flags declarations that are wholly const. + "prefer-const": ["error", { "destructuring": "all", "ignoreReadBeforeAssign": true }] + }, + "overrides": [ + { + // airbnb-react.json declares these in an `overrides` block, and an extended + // `overrides` entry outranks the extending config's top-level `rules` -- so + // re-tuning them has to happen in an `overrides` entry here too. + "files": ["**/*.ts", "**/*.tsx"], + "rules": { + // this package lays files out top-down -- exported component first, its local + // helpers below -- and hoisted function declarations make that legal + "no-use-before-define": [ + "error", + { "functions": false, "classes": true, "variables": true } + ], + // purely cosmetic, and its autofix makes JSX harder to read rather than easier: + // splitting `{cubeName} {name}` onto separate lines forces a literal + // `{' '}` line in to preserve the space, 61 times across this package + "@stylistic/jsx-one-expression-per-line": "off", + // airbnb lists `Link` here for react-router's, which this package does use -- + // always with `to`, so the rule has nothing to catch there. What it did catch is + // @cube-dev/ui-kit's unrelated `Link`, typed `Omit`: a + // Button alias driven by `onPress`, not an anchor. The rule cannot tell the two + // apart by import, and it still checks real `` elements with the list empty. + "jsx-a11y/anchor-is-valid": [ + "error", + { + "components": [], + "specialLink": ["to"], + "aspects": ["noHref", "invalidHref", "preferButton"] + } + ] + } + } + ] +} diff --git a/packages/cubejs-playground/package.json b/packages/cubejs-playground/package.json index 8188ae25bcba0..bbdcf5044c3b2 100644 --- a/packages/cubejs-playground/package.json +++ b/packages/cubejs-playground/package.json @@ -79,9 +79,6 @@ "@types/styled-components": "^5.1.34", "@vitejs/plugin-react": "^6", "antd": "4.16.13", - "eslint-config-airbnb": "^18.1.0", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.20.0", "fs-extra": "^11.1.1", "graphql": "^15.8.0", "jsdom": "^26", diff --git a/packages/cubejs-playground/postbuild.js b/packages/cubejs-playground/postbuild.js index 5a4978052aff6..ba77363e29d3d 100644 --- a/packages/cubejs-playground/postbuild.js +++ b/packages/cubejs-playground/postbuild.js @@ -17,4 +17,4 @@ const distFolder = path.resolve(__dirname, 'lib'); 2 ) ); -}); \ No newline at end of file +}); diff --git a/packages/cubejs-playground/src/App.tsx b/packages/cubejs-playground/src/App.tsx index 80d25e1e6143c..a47b952e00e13 100755 --- a/packages/cubejs-playground/src/App.tsx +++ b/packages/cubejs-playground/src/App.tsx @@ -44,12 +44,16 @@ class App extends Component, AppState> { return { fatalError: error }; } - state: AppState = { - fatalError: null, - context: null, - showLoader: false, - isAppContextSet: false, - }; + constructor(props: PropsWithChildren) { + super(props); + + this.state = { + fatalError: null, + context: null, + showLoader: false, + isAppContextSet: false, + }; + } async componentDidMount() { setTimeout(() => this.setState({ showLoader: true }), 700); diff --git a/packages/cubejs-playground/src/ChartContainer.tsx b/packages/cubejs-playground/src/ChartContainer.tsx index 05cf50ba1590f..8543683d8acd5 100644 --- a/packages/cubejs-playground/src/ChartContainer.tsx +++ b/packages/cubejs-playground/src/ChartContainer.tsx @@ -73,9 +73,7 @@ const UnsupportedFrameworkPlaceholder: UnsupportedPlaceholder = ({ href="https://cube.dev/reference/javascript-sdk/reference/cubejs-client-core" target="_blank" rel="noopener noreferrer" - onClick={() => - playgroundAction('Unsupported Framework Docs', { framework }) - } + onClick={() => playgroundAction('Unsupported Framework Docs', { framework })} > Vanilla JavaScript  docs @@ -160,12 +158,11 @@ class ChartContainer extends Component< static getDerivedStateFromProps(props, state) { if ( - props.isChartRendererReady && - props.iframeRef.current != null && - props.chartingLibrary + props.isChartRendererReady + && props.iframeRef.current != null + && props.chartingLibrary ) { - const { __cubejsPlayground } = - props.iframeRef.current.contentWindow || {}; + const { __cubejsPlayground } = props.iframeRef.current.contentWindow || {}; if (!__cubejsPlayground) { return { @@ -189,10 +186,9 @@ class ChartContainer extends Component< if (props.framework === 'react') { codeExample = codesandboxFiles['index.js']; } else if (props.framework === 'angular') { - codeExample = - codesandboxFiles[ - 'src/app/query-renderer/query-renderer.component.ts' - ]; + codeExample = codesandboxFiles[ + 'src/app/query-renderer/query-renderer.component.ts' + ]; } else if (props.framework === 'vue') { codeExample = codesandboxFiles['src/components/ChartRenderer.vue']; } @@ -226,7 +222,6 @@ class ChartContainer extends Component< dependencies, redirectToDashboard, activeTab, - addingToDashboard, chartRendererError, sql, } = this.state; @@ -257,28 +252,27 @@ class ChartContainer extends Component< const parameters = isChartRendererReady ? getParameters( - codeSandboxDefinition( - frameworkToTemplate[framework], - codesandboxFiles, - dependencies - ) + codeSandboxDefinition( + frameworkToTemplate[framework], + codesandboxFiles, + dependencies ) + ) : null; - const chartLibrariesMenu = - (chartLibraries[framework] || []).length > 0 ? ( - { - playgroundAction('Set Chart Library', { chartingLibrary: e.key }); - setChartLibrary(e.key); - }} - > - {(chartLibraries[framework] || []).map((library) => ( - {library.title} - ))} - - ) : null; + const chartLibrariesMenu = (chartLibraries[framework] || []).length > 0 ? ( + { + playgroundAction('Set Chart Library', { chartingLibrary: e.key }); + setChartLibrary(e.key); + }} + > + {(chartLibraries[framework] || []).map((library) => ( + {library.title} + ))} + + ) : null; const frameworkMenu = ( - } + )} > { - this.setState({ sql }); + onChange={(nextSql) => { + this.setState({ sql: nextSql }); }} /> @@ -472,11 +466,11 @@ class ChartContainer extends Component< } else if (activeTab === 'cache') { return ( - } + )} > @@ -488,11 +482,11 @@ class ChartContainer extends Component< return ( - } + )} > -
{overlay}
+
{overlay}
); } diff --git a/packages/cubejs-playground/src/QueryBuilder/FilterGroup.tsx b/packages/cubejs-playground/src/QueryBuilder/FilterGroup.tsx index 956120c81a9c3..9242bd3c40c21 100644 --- a/packages/cubejs-playground/src/QueryBuilder/FilterGroup.tsx +++ b/packages/cubejs-playground/src/QueryBuilder/FilterGroup.tsx @@ -25,15 +25,14 @@ const FilterGroup = ({ updateMethods, missingMembers, }: Props) => { - const operatorsByMemberName = useDeepMemo(() => { - return members.reduce( - (memo, item) => ({ - ...memo, - [item.member]: [...(memo[item.member] || []), item.operator], - }), - {} - ); - }, [members]); + const operatorsByMemberName = useDeepMemo(() => members.reduce( + (memo, item) => ({ + ...memo, + [item.member]: [...(memo[item.member] || []), item.operator], + }), + {} + ), + [members]); return ( @@ -53,9 +52,7 @@ const FilterGroup = ({ style={{ minWidth: 150, }} - onClick={(updateWith) => - updateMethods.update(m, { ...m, dimension: updateWith }) - } + onClick={(updateWith) => updateMethods.update(m, { ...m, dimension: updateWith })} > {m.dimension.title} @@ -74,9 +71,7 @@ const FilterGroup = ({ disabled={disabled} value={m.operator} style={{ width: 200 }} - onChange={(operator) => - updateMethods.update(m, { ...m, operator }) - } + onChange={(operator) => updateMethods.update(m, { ...m, operator })} > {m.operators.map((operator) => { const isOperatorDisabled = operatorsByMemberName[ @@ -90,8 +85,8 @@ const FilterGroup = ({ title={ isOperatorDisabled ? `There is already a filter applied with this operator for ${ - m.dimension?.title || m.name - }` + m.dimension?.title || m.name + }` : operator.name } disabled={isOperatorDisabled} diff --git a/packages/cubejs-playground/src/QueryBuilder/FilterInput.tsx b/packages/cubejs-playground/src/QueryBuilder/FilterInput.tsx index 4b12f63d637ea..3616a049f7298 100644 --- a/packages/cubejs-playground/src/QueryBuilder/FilterInput.tsx +++ b/packages/cubejs-playground/src/QueryBuilder/FilterInput.tsx @@ -35,8 +35,8 @@ export default function FilterInput({ const Filter = FilterInputs[member.dimension.type] || FilterInputs.string; const ref = useRef( - debounce<(member: any, values: string[]) => void>(500, (member, values) => { - updateMethods.update(member, { ...member, values }); + debounce<(member: any, values: string[]) => void>(500, (filterMember, values) => { + updateMethods.update(filterMember, { ...filterMember, values }); }) ); const [values, setValues] = useState(member.values); @@ -46,9 +46,9 @@ export default function FilterInput({ key="filter" disabled={disabled} values={values} - onChange={(values) => { - setValues(values); - ref.current(member, values); + onChange={(nextValues) => { + setValues(nextValues); + ref.current(member, nextValues); }} /> ); diff --git a/packages/cubejs-playground/src/QueryBuilder/MemberDropdown.tsx b/packages/cubejs-playground/src/QueryBuilder/MemberDropdown.tsx index c5011f976d034..e9add9215a335 100644 --- a/packages/cubejs-playground/src/QueryBuilder/MemberDropdown.tsx +++ b/packages/cubejs-playground/src/QueryBuilder/MemberDropdown.tsx @@ -57,12 +57,12 @@ function filterMembersByKeys( return members .filter(({ cubeName }) => cubeNames.includes(cubeName)) - .map((cube) => { - return { + .map((cube) => ( + { ...cube, members: cube.members.filter(({ name }) => keys.includes(name)), - }; - }); + } + )); } type MemberDropdownProps = { @@ -85,9 +85,7 @@ export default function MemberMenu({ const hasMembers = availableCubes.some((cube) => cube.members.length > 0); const indexedMembers = useDeepMemo(() => { - getNameMemberPairs(availableCubes).forEach(([name, { title }]) => - index.add(name as any, title) - ); + getNameMemberPairs(availableCubes).forEach(([name, { title }]) => index.add(name as any, title)); return Object.fromEntries(getNameMemberPairs(availableCubes)); }, [availableCubes]); @@ -136,7 +134,7 @@ export default function MemberMenu({ searchInputRef.current?.focus({ preventScroll: true }); }); }} - overlay={ + overlay={(
- {hasMembers ? ( + {hasMembers && ( <> - ) : showNoMembersPlaceholder ? ( + )} + {!hasMembers && showNoMembersPlaceholder && ( No members found - ) : null} + )}
- } + )} /> ); } diff --git a/packages/cubejs-playground/src/QueryBuilder/TimeGroup.tsx b/packages/cubejs-playground/src/QueryBuilder/TimeGroup.tsx index d758b27d5ba8e..9bad2305fa6d8 100644 --- a/packages/cubejs-playground/src/QueryBuilder/TimeGroup.tsx +++ b/packages/cubejs-playground/src/QueryBuilder/TimeGroup.tsx @@ -107,9 +107,7 @@ const TimeGroup = ({ data-testid="TimeDimension" disabled={disabled} availableCubes={availableMembers} - onClick={(updateWith) => - updateMethods.update(m, { ...m, dimension: updateWith }) - } + onClick={(updateWith) => updateMethods.update(m, { ...m, dimension: updateWith })} > {m.dimension.title} @@ -117,7 +115,7 @@ const TimeGroup = ({ ); return ( - + {isMissing ? ( {buttonGroup} ) : ( @@ -162,17 +160,17 @@ const TimeGroup = ({ + overlay={granularityMenu(m.dimension, (granularity) => ( updateMethods.update(m, { ...m, granularity: granularity.name }) - )} + ))} onOverlayOpen={() => setGranularityShown(true)} onOverlayClose={() => setGranularityShown(false)} onItemClick={() => setGranularityShown(false)} > {m.dimension.granularities.find( (g) => g.name === m.granularity - ) && - m.dimension.granularities.find((g) => g.name === m.granularity) + ) + && m.dimension.granularities.find((g) => g.name === m.granularity) .title} @@ -186,9 +184,7 @@ const TimeGroup = ({ availableCubes={availableMembers} type="dashed" icon={} - onClick={(member) => - updateMethods.add({ dimension: member, granularity: 'day' }) - } + onClick={(member) => updateMethods.add({ dimension: member, granularity: 'day' })} > {addMemberName} diff --git a/packages/cubejs-playground/src/QueryBuilder/TimeRangeSelector.tsx b/packages/cubejs-playground/src/QueryBuilder/TimeRangeSelector.tsx index d25846d3796e1..6320763ff8688 100644 --- a/packages/cubejs-playground/src/QueryBuilder/TimeRangeSelector.tsx +++ b/packages/cubejs-playground/src/QueryBuilder/TimeRangeSelector.tsx @@ -26,9 +26,9 @@ export function TimeDateRangeSelector(props: TimeDateRangeSelectorProps) { return startDate && endDate ? { - start: startDate, - end: endDate, - } + start: startDate, + end: endDate, + } : null; }, [value[0], value[1]]); diff --git a/packages/cubejs-playground/src/QueryBuilderV2/Pivot/DroppableArea.tsx b/packages/cubejs-playground/src/QueryBuilderV2/Pivot/DroppableArea.tsx index 117b136874431..f5b10659035aa 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/Pivot/DroppableArea.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/Pivot/DroppableArea.tsx @@ -18,9 +18,7 @@ const HeaderElement = tasty({ }, }); -const Header = memo(({ axis }: { axis: string }) => { - return {axis.toUpperCase()} axis; -}); +const Header = memo(({ axis }: { axis: string }) => {axis.toUpperCase()} axis); export function PivotDroppableArea({ pivotConfig, @@ -44,11 +42,11 @@ export function PivotDroppableArea({ {/* @ts-ignore */} {pivotConfig[axis].map((id, index) => { - let type: 'timeDimension' | 'dimension' | 'measure' = id.includes('.') - ? id.split('.').length === 3 - ? 'timeDimension' - : 'dimension' - : 'measure'; + let type: 'timeDimension' | 'dimension' | 'measure' = 'measure'; + + if (id.includes('.')) { + type = id.split('.').length === 3 ? 'timeDimension' : 'dimension'; + } return ; })} diff --git a/packages/cubejs-playground/src/QueryBuilderV2/Pivot/Options.tsx b/packages/cubejs-playground/src/QueryBuilderV2/Pivot/Options.tsx index 58647f710f364..33d1b150ea9da 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/Pivot/Options.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/Pivot/Options.tsx @@ -12,11 +12,9 @@ export function PivotOptions({ return pivotConfig ? ( - onUpdate({ - fillMissingDates: !pivotConfig.fillMissingDates, - }) - } + onChange={() => onUpdate({ + fillMissingDates: !pivotConfig.fillMissingDates, + })} > Fill Missing Dates diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilder.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilder.tsx index 65417c039765d..6497364141fbe 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilder.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilder.tsx @@ -36,13 +36,12 @@ export function QueryBuilder( disableSidebarResizing, } = props; - const cubeApi = useMemo(() => { - return apiUrl && apiToken && apiToken !== 'undefined' - ? cube(apiToken, { - apiUrl, - }) - : undefined; - }, [apiUrl, apiToken]); + const cubeApi = useMemo(() => (apiUrl && apiToken && apiToken !== 'undefined' + ? cube(apiToken, { + apiUrl, + }) + : undefined), + [apiUrl, apiToken]); const [storedTimezones] = useLocalStorage('QueryBuilder:timezones', []); @@ -50,8 +49,10 @@ export function QueryBuilder( const queryCopy = JSON.parse(JSON.stringify(query)); // add the last stored timezone if the query is empty - if (JSON.stringify(queryCopy) === '{}' && storedTimezones[0]) { - queryCopy.timezone = storedTimezones[0]; + const [lastStoredTimezone] = storedTimezones; + + if (JSON.stringify(queryCopy) === '{}' && lastStoredTimezone) { + queryCopy.timezone = lastStoredTimezone; } return queryCopy; @@ -85,13 +86,12 @@ export function QueryBuilder( useEffect(() => { if (defaultQuery && shouldRunDefaultQuery && meta) { - void runQuery(); + runQuery(); } }, [shouldRunDefaultQuery, meta]); - useCommitPress(() => { - return runQuery(); - }, true); + useCommitPress(() => runQuery(), + true); if (!apiToken || !cubeApi || !apiUrl) { return null; @@ -123,7 +123,7 @@ export function QueryBuilder( ...otherProps, }} > - {!meta ? ( + {!meta && ( {!metaError ? ( Loading meta information... @@ -134,11 +134,8 @@ export function QueryBuilder( )} - ) : props.children ? ( - props.children - ) : ( - )} + {!!meta && (props.children || )} ); } diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderChart.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderChart.tsx index dc4fcca35c7a3..3549713e85729 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderChart.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderChart.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState, ReactElement } from 'react'; import { Button, Dialog, @@ -28,9 +28,6 @@ import { AccordionCard } from './components/AccordionCard'; import { OutdatedLabel } from './components/OutdatedLabel'; import { QueryBuilderChartResults } from './QueryBuilderChartResults'; -const CHART_HEIGHT = 400; -const MAX_SERIES_LIMIT = 25; - interface QueryBuilderChartProps { maxHeight?: number; onToggle?: (isExpanded: boolean) => void; @@ -41,7 +38,7 @@ const ALLOWED_CHART_TYPES = ['table', 'line', 'bar', 'area']; export function QueryBuilderChart(props: QueryBuilderChartProps) { const [isVizardLoaded, setIsVizardLoaded] = useState(false); const [isExpanded, setIsExpanded] = useLocalStorage('QueryBuilder:Chart:expanded', false); - const { maxHeight = CHART_HEIGHT, onToggle } = props; + const { onToggle } = props; let { query, isLoading, @@ -101,50 +98,45 @@ export function QueryBuilderChart(props: QueryBuilderChartProps) { ); const onMove = useCallback( - (arg) => { - return updatePivotConfig.moveItem(arg); - }, + (arg) => updatePivotConfig.moveItem(arg), [updatePivotConfig] ); const onUpdate = useCallback( - (arg) => { - return updatePivotConfig.update(arg); - }, + (arg) => updatePivotConfig.update(arg), [updatePivotConfig] ); - const pivotConfigurator = useMemo(() => { - return pivotConfig ? ( - - - - - -
- -
-
-
- ) : undefined; - }, [pivotConfig, onMove, onUpdate]); + const pivotConfigurator = useMemo(() => (pivotConfig ? ( + + + + + +
+ +
+
+
+ ) : undefined), + [pivotConfig, onMove, onUpdate]); + + let subtitle: ReactElement | undefined; + + if (isLoading && isExpanded) { + subtitle = ; + } else if (!isLoading && isResultOutdated) { + subtitle = ; + } return ( - ) : undefined - ) : isResultOutdated ? ( - - ) : undefined - } + subtitle={subtitle} extra={ isExpanded ? ( @@ -193,8 +185,8 @@ export function QueryBuilderChart(props: QueryBuilderChartProps) { > Code - {/**/} - {/**/} + {/* */} + {/* */}
Chart Prototyping diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx index e8ccc650abfb1..74ef99b087ab6 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx @@ -9,7 +9,6 @@ import { DownIcon, Flow, Grid, - InfoCircleIcon, Link, NumberInput, Radio, @@ -19,7 +18,6 @@ import { tasty, Text, Title, - TooltipProvider, } from '@cube-dev/ui-kit'; import { forwardRef, Key, useEffect, useMemo, useState } from 'react'; import { DragDropContext, Draggable, Droppable, OnDragEndResponder } from 'react-beautiful-dnd'; @@ -64,7 +62,7 @@ function timezoneByName(name: string) { return { tzCode: name, label: name, - name: name, + name, utc: '', }; } @@ -176,7 +174,7 @@ type OrderListItemProps = { onSortChange: (name: string, sorting: SortDirection) => void; }; -export const OrderListItem = forwardRef(function OrderListItem(props: OrderListItemProps, ref) { +export const OrderListItem = forwardRef((props: OrderListItemProps, ref) => { const { name, memberType, @@ -221,8 +219,7 @@ export function QueryBuilderExtras() { const [showOrder, setShowOrder] = useState(true); const fields = [...(query?.dimensions ?? []), ...(query?.measures ?? [])]; const storedTimezones = useStoredTimezones(query.timezone); - const timeDimensions = - query?.timeDimensions?.filter((time) => time.granularity).map((time) => time.dimension) ?? []; + const timeDimensions = query?.timeDimensions?.filter((time) => time.granularity).map((time) => time.dimension) ?? []; timeDimensions.forEach((name) => { if (name && !fields.includes(name)) { @@ -317,18 +314,16 @@ export function QueryBuilderExtras() { const optionsPopover = useMemo(() => { // ungrouped - const isSelected = - query.ungrouped || - query.total || - query.timezone || - query.offset || - (query.limit && query.limit !== DEFAULT_LIMIT); - const selectedCount = - (query.ungrouped ? 1 : 0) + - (query.total ? 1 : 0) + - (query.timezone ? 1 : 0) + - (query.limit && query.limit !== DEFAULT_LIMIT ? 1 : 0) + - (query.offset ? 1 : 0); + const isSelected = query.ungrouped + || query.total + || query.timezone + || query.offset + || (query.limit && query.limit !== DEFAULT_LIMIT); + const selectedCount = (query.ungrouped ? 1 : 0) + + (query.total ? 1 : 0) + + (query.timezone ? 1 : 0) + + (query.limit && query.limit !== DEFAULT_LIMIT ? 1 : 0) + + (query.offset ? 1 : 0); // timezone const timezone = query?.timezone || ''; @@ -425,10 +420,10 @@ export function QueryBuilderExtras() { return; } - const timezone = val as string; + const nextTimezone = val as string; updateQuery(() => ({ - timezone: timezone === '' ? undefined : timezone, + timezone: nextTimezone === '' ? undefined : nextTimezone, })); close(); @@ -596,13 +591,13 @@ export function QueryBuilderLimitSelect() { ) : null } - labelSuffix={ + labelSuffix={( - } + )} selectedKey={query.limit == null ? '0' : String(query.limit)} onSelectionChange={(val: Key) => { updateQuery(() => ({ limit: val === '0' ? undefined : Number(val as string) })); diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderFilters.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderFilters.tsx index d859a2bbe1738..5e6de5761f857 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderFilters.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderFilters.tsx @@ -118,27 +118,27 @@ export function QueryBuilderFilters({ onToggle }: { onToggle?: (isExpanded: bool ) : null } contentStyles={{ border: 'top' }} - onToggle={(isExpanded) => { - setIsExpanded(isExpanded); - onToggle?.(isExpanded); + onToggle={(nextIsExpanded) => { + setIsExpanded(nextIsExpanded); + onToggle?.(nextIsExpanded); }} > {dateRanges.list.map((dimensionName, i) => { const timeDimension = timeDimensions.find( - (timeDimension) => timeDimension.dimension === dimensionName + (candidateTimeDimension) => candidateTimeDimension.dimension === dimensionName ); const dimension = members.dimensions[dimensionName]; const cubeName = dimensionName.split('.')[0]; - const cube = cubes.find((cube) => cube.name === cubeName); + const cube = cubes.find((candidateCube) => candidateCube.name === cubeName); const memberName = dimensionName.split('.')[1]; const member = members.measures[dimensionName] || members.dimensions[dimensionName]; return ( { filtersUpdater.remove(index); }} - onChange={(filter) => { - filtersUpdater.update(index, filter); + onChange={(nextFilter) => { + filtersUpdater.update(index, nextFilter); }} onUnwrap={() => { if (filter.and.length === 1) { @@ -180,8 +182,8 @@ export function QueryBuilderFilters({ onToggle }: { onToggle?: (isExpanded: bool } filtersUpdater.remove(index); - filter.and.forEach((filter) => { - filtersUpdater.add(filter); + filter.and.forEach((subFilter) => { + filtersUpdater.add(subFilter); }); }} /> @@ -191,6 +193,8 @@ export function QueryBuilderFilters({ onToggle }: { onToggle?: (isExpanded: bool if ('or' in filter) { return ( { filtersUpdater.remove(index); }} - onChange={(filter) => { - filtersUpdater.update(index, filter); + onChange={(nextFilter) => { + filtersUpdater.update(index, nextFilter); }} onUnwrap={() => { if (filter.or.length === 1) { @@ -210,8 +214,8 @@ export function QueryBuilderFilters({ onToggle }: { onToggle?: (isExpanded: bool } filtersUpdater.remove(index); - filter.or.forEach((filter) => { - filtersUpdater.add(filter); + filter.or.forEach((subFilter) => { + filtersUpdater.add(subFilter); }); }} /> @@ -224,12 +228,14 @@ export function QueryBuilderFilters({ onToggle }: { onToggle?: (isExpanded: bool const memberFullName = filter.member; const cubeName = memberFullName.split('.')[0]; - const cube = cubes.find((cube) => cube.name === cubeName); + const cube = cubes.find((candidateCube) => candidateCube.name === cubeName); const memberName = memberFullName.split('.')[1]; const member = members.measures[memberFullName] || members.dimensions[memberFullName]; return ( { const member = members.segments[segment]; const cubeName = segment.split('.')[0]; - const cube = cubes.find((cube) => cube.name === cubeName); + const cube = cubes.find((candidateCube) => candidateCube.name === cubeName); const memberName = segment.split('.')[1]; return ( diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGeneratedSQL.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGeneratedSQL.tsx index 4aca4cf6e5735..0bf5f57ab7655 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGeneratedSQL.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGeneratedSQL.tsx @@ -16,8 +16,7 @@ const EditSQLQueryButton = tasty(Button, { }); export function QueryBuilderGeneratedSQL() { - let { query, queryHash, cubeApi, isQueryEmpty, verificationError, openSqlRunner } = - useQueryBuilderContext(); + const { query, queryHash, cubeApi, isQueryEmpty, verificationError, openSqlRunner } = useQueryBuilderContext(); return useDeepMemo(() => { if (!isQueryEmpty) { @@ -44,12 +43,12 @@ export function QueryBuilderGeneratedSQL() { } // in the case of a compareDateRange query the SQL will be the same - const [query] = Array.isArray(sqlQuery) ? sqlQuery : [sqlQuery]; - const value = query && sqlFormatter.format(query.sql()); + const [firstSqlQuery] = Array.isArray(sqlQuery) ? sqlQuery : [sqlQuery]; + const value = firstSqlQuery && sqlFormatter.format(firstSqlQuery.sql()); return ( Copy @@ -58,7 +57,7 @@ export function QueryBuilderGeneratedSQL() { openSqlRunner?.(value)} /> ) : undefined} - } + )} > diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGraphQL.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGraphQL.tsx index b0d1a458e9b25..6461e84aba467 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGraphQL.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderGraphQL.tsx @@ -104,18 +104,17 @@ export function QueryBuilderGraphQL() { }, [queryHash]); return useMemo(() => { - let fetchButton = - !rawData && !queryError ? ( - - ) : null; + let fetchButton = !rawData && !queryError ? ( + + ) : null; if (hasPrivateMembers && fetchButton) { fetchButton = ( @@ -135,11 +134,11 @@ export function QueryBuilderGraphQL() { ) : ( Copy - } + )} extraActions={fetchButton} > diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderInternals.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderInternals.tsx index 670e2cb53cc49..a56402399c185 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderInternals.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderInternals.tsx @@ -28,7 +28,7 @@ const Divider = tasty({ }, }); -type Tab = 'results' | 'generated-sql' | 'json' | 'graphql' | 'sql'; +type TabId = 'results' | 'generated-sql' | 'json' | 'graphql' | 'sql'; const QueryBuilderPanel = tasty(Panel, { isFlex: true, @@ -41,48 +41,47 @@ const QueryBuilderPanel = tasty(Panel, { }, }); -const QueryBuilderInternals = memo(function QueryBuilderInternals() { +const QueryBuilderInternals = memo(() => { const { error, resultSet, queryHash, dateRanges } = useQueryBuilderContext(); const [isChartExpanded, setIsChartExpanded] = useLocalStorage( 'QueryBuilder:Chart:expanded', false ); - const [tab, setTab] = useState('results'); + const [tab, setTab] = useState('results'); const ref = useRef(null); const chartRef = useRef(null); const [isFiltersExpanded, setIsFiltersExpanded] = useState(true); const [chartSize, updateChartSize] = useAutoSize(chartRef, 0); - const ResultsAndSQL = useMemo(() => { - return ( - <> - - - } - styles={{ padding: '0 1x' }} - onChange={(tab: string) => setTab(tab as Tab)} - > - - - - - - - - - - - - - - - - - - ); - }, [tab, isChartExpanded]); + const ResultsAndSQL = useMemo(() => ( + <> + + + } + styles={{ padding: '0 1x' }} + onChange={(nextTab: string) => setTab(nextTab as TabId)} + > + + + + + + + + + + + + + + + + + + ), + [tab, isChartExpanded]); const onToggle = useEvent((isExpanded: boolean) => { setIsFiltersExpanded(isExpanded); @@ -123,25 +122,24 @@ const QueryBuilderInternals = memo(function QueryBuilderInternals() { [] )} - {useMemo(() => { - return ( - <> -
- -
- {!isChartExpanded || chartSize > CHART_THRESHOLD ? ( - ResultsAndSQL - ) : ( - - - - - - - )} - - ); - }, [isChartExpanded, chartSize, ResultsAndSQL])} + {useMemo(() => ( + <> +
+ +
+ {!isChartExpanded || chartSize > CHART_THRESHOLD ? ( + ResultsAndSQL + ) : ( + + + + + + + )} + + ), + [isChartExpanded, chartSize, ResultsAndSQL])} diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderRest.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderRest.tsx index 92e5866cbb97d..a2cd0f0fec40f 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderRest.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderRest.tsx @@ -18,11 +18,11 @@ export function QueryBuilderRest() { ) : ( Copy - } + )} > diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderResults.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderResults.tsx index 7132118232ec2..4b1593ac21bbf 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderResults.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderResults.tsx @@ -168,13 +168,15 @@ function renderValue(value: string | number | null | undefined, fallback?: strin } } - return typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}') ? ( - {value.replaceAll(/[{}]+/g, '')} - ) : fallback ? ( - value || {fallback} - ) : ( - value - ); + if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) { + return {value.replaceAll(/[{}]+/g, '')}; + } + + if (fallback) { + return value || {fallback}; + } + + return value; } function Pagination(props: PaginationProps) { @@ -205,13 +207,12 @@ function Pagination(props: PaginationProps) { width="min 15x" onSelectionChange={onSelectionChange} > - {[...Array(numberOfPages)].map((a, i) => { - return ( - - {getPaginationOptionLabel({ page: i + 1, perPage, total })} - - ); - })} + {[...Array(numberOfPages)].map((a, i) => ( + // eslint-disable-next-line react/no-array-index-key + + {getPaginationOptionLabel({ page: i + 1, perPage, total })} + + ))} - - )} - {isVerifying || isMetaLoading ? : null} - - - + const topBar = useMemo(() => ( + + + {showEditQueryButton ? editQueryButton : null} + {!usedCubes.length ? ( + All members + ) : ( + - + )} + {isVerifying || isMetaLoading ? : null} - ); - }, [viewMode, isQueryEmpty, isMetaLoading, usedMembers.length, appliedFilterString, isVerifying]); + + + + + + + ), + [viewMode, isQueryEmpty, isMetaLoading, usedMembers.length, appliedFilterString, isVerifying]); const content = ( <> setIsPasteDialogOpen(false)}> diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderToolBar.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderToolBar.tsx index ed4f0e80a15d3..cf39dc884e899 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderToolBar.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderToolBar.tsx @@ -68,12 +68,12 @@ export function QueryBuilderToolBar() { + Enter OR{' '} Ctrl + Enter - } + )} >