This is an automated email from the ASF dual-hosted git repository.
tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new bbb7d937c8 [ZEPPELIN-6372] Reimplement custom TSLint rules as ESLint
rules
bbb7d937c8 is described below
commit bbb7d937c8f2cdedb574f721b27bde7a19c33929
Author: YONGJAE LEE (이용재) <[email protected]>
AuthorDate: Sat Jul 11 14:38:12 2026 +0900
[ZEPPELIN-6372] Reimplement custom TSLint rules as ESLint rules
### What is this PR for?
PR #5109 removed all TSLint code from zeppelin-web-angular. This restores
the two custom lint rules on the ESLint flat config as a local plugin at
`zeppelin-web-angular/eslint-rules`:
- **constructor-params-order** (ZEPPELIN-6301): constructor parameters must
be ordered public, protected, private, with optional and `<at>Optional()`
parameters last.
- **ordered-exports** (ZEPPELIN-6325): top-level exports in `public-api.ts`
barrels must be alphabetically ordered by module specifier.
Both are registered in `eslint.config.js` and enforced by the existing
husky/lint-staged pre-commit hook that runs `eslint --fix` on staged `.ts`
files. Logic and autofix are ported from the original TSLint rules onto the
typescript-eslint AST. The rules are plain CommonJS, matching eslint.config.js.
`eslint src projects` reports no violations on the current tree, so no
existing code changes are required.
### What type of PR is it?
Improvement
### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-6372
### How should this be tested?
* `cd zeppelin-web-angular && npm run lint` passes.
* Add an out-of-order constructor parameter, or an unsorted export in a
`public-api.ts`, then run `eslint --fix` and confirm the rule flags and
reorders it.
### Questions:
* Does the license files need to update? No.
* Is there breaking changes for older versions? No.
* Does this needs documentation? No.
Closes #5279 from voidmatcha/ZEPPELIN-6372.
Signed-off-by: ChanHo Lee <[email protected]>
---
.../eslint-rules/constructor-params-order.js | 173 ++++++++++++++++++++
.../eslint-rules/constructor-params-order.test.js | 84 ++++++++++
zeppelin-web-angular/eslint-rules/index.js | 24 +++
zeppelin-web-angular/eslint.config.js | 22 ++-
zeppelin-web-angular/package-lock.json | 182 +++++++++++++++++++++
zeppelin-web-angular/package.json | 2 +
6 files changed, 486 insertions(+), 1 deletion(-)
diff --git a/zeppelin-web-angular/eslint-rules/constructor-params-order.js
b/zeppelin-web-angular/eslint-rules/constructor-params-order.js
new file mode 100644
index 0000000000..2ead398082
--- /dev/null
+++ b/zeppelin-web-angular/eslint-rules/constructor-params-order.js
@@ -0,0 +1,173 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// ESLint reimplementation of the former custom TSLint
`constructor-params-order`
+// rule (ZEPPELIN-6301). Enforces the constructor parameter order
+// public -> protected -> private (-> none -> optional) for a consistent New UI
+// constructor style. ESLint replaces TSLint under ZEPPELIN-6372.
+
+'use strict';
+
+const RANK = { public: 0, protected: 1, private: 2, none: 3, optional: 4 };
+
+/**
+ * Collect decorators attached to a (possibly parameter-property) parameter
node.
+ * @param {any} param
+ * @returns {any[]}
+ */
+function getDecorators(param) {
+ if (Array.isArray(param.decorators) && param.decorators.length > 0) {
+ return param.decorators;
+ }
+ // TSParameterProperty wraps the real parameter in `.parameter`.
+ if (param.parameter && Array.isArray(param.parameter.decorators)) {
+ return param.parameter.decorators;
+ }
+ return [];
+}
+
+/**
+ * True when the parameter is optional -- either annotated with `@Optional()`
+ * (Angular DI) or declared with a `?` question token.
+ * @param {any} param
+ * @returns {boolean}
+ */
+function isOptional(param) {
+ const hasOptionalDecorator = getDecorators(param).some(decorator => {
+ let expr = decorator.expression;
+ if (expr && expr.type === 'CallExpression') {
+ expr = expr.callee;
+ }
+ return expr && expr.type === 'Identifier' && expr.name === 'Optional';
+ });
+ if (hasOptionalDecorator) {
+ return true;
+ }
+ const inner = param.type === 'TSParameterProperty' ? param.parameter : param;
+ return !!(inner && inner.optional);
+}
+
+/**
+ * Map a constructor parameter to its accessibility rank.
+ * @param {any} param
+ * @returns {number}
+ */
+function getRank(param) {
+ if (isOptional(param)) {
+ return RANK.optional;
+ }
+ if (param.type === 'TSParameterProperty') {
+ // A parameter property with no explicit accessibility (e.g. `readonly x`)
+ // is implicitly public.
+ return RANK[param.accessibility || 'public'];
+ }
+ return RANK.none;
+}
+
+/**
+ * Source range of a parameter's "content": the parameter itself plus its
+ * leading decorators/comments and any trailing comment that sits before the
+ * following comma. Reordering moves these content ranges between fixed slots
+ * (keeping the original commas and whitespace in place), so comments travel
+ * with their parameter -- the same guarantee the original TSLint fixer gave by
+ * slicing from getFullStart() to getFullStart().
+ * @param {any} param
+ * @param {import('eslint').SourceCode} sourceCode
+ * @returns {[number, number]}
+ */
+function getContentRange(param, sourceCode) {
+ let start = param.range[0];
+ let headNode = param;
+ for (const decorator of getDecorators(param)) {
+ if (decorator.range[0] < start) {
+ start = decorator.range[0];
+ headNode = decorator;
+ }
+ }
+ const leading = sourceCode.getCommentsBefore(headNode);
+ if (leading.length > 0 && leading[0].range[0] < start) {
+ start = leading[0].range[0];
+ }
+ let end = param.range[1];
+ const nextToken = sourceCode.getTokenAfter(param);
+ for (const comment of sourceCode.getCommentsAfter(param)) {
+ if (!nextToken || comment.range[1] <= nextToken.range[0]) {
+ end = Math.max(end, comment.range[1]);
+ }
+ }
+ return [start, end];
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'Enforce constructor parameter order: public, protected,
private'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ order: 'Constructor parameters should be ordered: public, protected,
private'
+ }
+ },
+
+ create(context) {
+ const sourceCode = context.sourceCode || context.getSourceCode();
+
+ /**
+ * @param {any} node MethodDefinition with kind === 'constructor'
+ */
+ function checkConstructor(node) {
+ const params = node.value.params;
+ if (!params || params.length <= 1) {
+ return;
+ }
+
+ const ranks = params.map(getRank);
+ const inOrder = ranks.every((rank, i) => i === 0 || ranks[i - 1] <=
rank);
+ if (inOrder) {
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: 'order',
+ fix(fixer) {
+ const text = sourceCode.getText();
+ const ranges = params.map(p => getContentRange(p, sourceCode));
+ // Stable sort by rank preserves the original relative order within a
+ // group, matching the previous TSLint behaviour.
+ const order = params.map((_, i) => i).sort((a, b) => ranks[a] -
ranks[b]);
+
+ // Drop the sorted parameter content into the fixed slots, keeping
the
+ // original separators (commas, whitespace, indentation) between
slots
+ // untouched. Only the parameter text moves, so formatting and inter-
+ // parameter comments are preserved.
+ let out = '';
+ for (let slot = 0; slot < params.length; slot++) {
+ const src = ranges[order[slot]];
+ out += text.slice(src[0], src[1]);
+ if (slot < params.length - 1) {
+ out += text.slice(ranges[slot][1], ranges[slot + 1][0]);
+ }
+ }
+ return fixer.replaceTextRange([ranges[0][0], ranges[params.length -
1][1]], out);
+ }
+ });
+ }
+
+ return {
+ 'MethodDefinition[kind="constructor"]': checkConstructor
+ };
+ }
+};
diff --git a/zeppelin-web-angular/eslint-rules/constructor-params-order.test.js
b/zeppelin-web-angular/eslint-rules/constructor-params-order.test.js
new file mode 100644
index 0000000000..3ead15d168
--- /dev/null
+++ b/zeppelin-web-angular/eslint-rules/constructor-params-order.test.js
@@ -0,0 +1,84 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Regression suite for the constructor-params-order fixer. It rewrites source
+// files unattended via lint-staged on every commit, so the tricky reordering
+// cases (comments, multi-line) are locked down here. Run with
+// `npm run test:eslint-rules` (Node's built-in runner, no extra deps).
+
+'use strict';
+
+const test = require('node:test');
+const { RuleTester } = require('eslint');
+const tsParser = require('@typescript-eslint/parser');
+const rule = require('./constructor-params-order');
+
+const ruleTester = new RuleTester({
+ languageOptions: { parser: tsParser, ecmaVersion: 2022, sourceType: 'module'
}
+});
+
+test('constructor-params-order', () => {
+ ruleTester.run('constructor-params-order', rule, {
+ valid: [
+ 'class A { constructor(public a: X, protected b: Y, private c: Z) {} }',
+ 'class A { constructor(private a: X) {} }',
+ 'class A { constructor() {} }',
+ // optional (@Optional() / question token) ranks last, so these are
ordered
+ 'class A { constructor(public a: X, private c: Z, @Optional() private d:
W) {} }',
+ 'class A { constructor(private a: X, @Optional() @Inject(T) public b: Y)
{} }',
+ 'class A { constructor(private a: X, b?: Y) {} }',
+ // a `readonly`-only parameter property is implicitly public, so it
ranks first
+ 'class A { constructor(readonly a: X, private b: Y) {} }'
+ ],
+ invalid: [
+ {
+ code: 'class A { constructor(private c: Z, public a: X) {} }',
+ output: 'class A { constructor(public a: X, private c: Z) {} }',
+ errors: [{ messageId: 'order' }]
+ },
+ {
+ code: 'class A { constructor(private c: Z, protected b: Y, public a:
X) {} }',
+ output: 'class A { constructor(public a: X, protected b: Y, private c:
Z) {} }',
+ errors: [{ messageId: 'order' }]
+ },
+ {
+ code: 'class A { constructor(@Optional() private a: X, public b: Y) {}
}',
+ output: 'class A { constructor(public b: Y, @Optional() private a: X)
{} }',
+ errors: [{ messageId: 'order' }]
+ },
+ // a trailing comment before the comma must travel with its parameter
+ {
+ code: 'class A { constructor(private b: B /* injected lazily */,
public a: A) {} }',
+ output: 'class A { constructor(public a: A, private b: B /* injected
lazily */) {} }',
+ errors: [{ messageId: 'order' }]
+ },
+ // multi-line: original commas / indentation are preserved
+ {
+ code: 'class A {\n constructor(\n private c: Z,\n public a: X\n
) {}\n}',
+ output: 'class A {\n constructor(\n public a: X,\n private c:
Z\n ) {}\n}',
+ errors: [{ messageId: 'order' }]
+ },
+ // a leading block comment stays attached to its parameter
+ {
+ code: 'class A {\n constructor(\n private a: A,\n /* keep */
public b: B\n ) {}\n}',
+ output: 'class A {\n constructor(\n /* keep */ public b: B,\n
private a: A\n ) {}\n}',
+ errors: [{ messageId: 'order' }]
+ },
+ // a `readonly`-only parameter property is public and must precede
private
+ {
+ code: 'class A { constructor(private a: A, readonly b: B) {} }',
+ output: 'class A { constructor(readonly b: B, private a: A) {} }',
+ errors: [{ messageId: 'order' }]
+ }
+ ]
+ });
+});
diff --git a/zeppelin-web-angular/eslint-rules/index.js
b/zeppelin-web-angular/eslint-rules/index.js
new file mode 100644
index 0000000000..eb787baa3f
--- /dev/null
+++ b/zeppelin-web-angular/eslint-rules/index.js
@@ -0,0 +1,24 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Local ESLint plugin for the project's Angular-specific constructor parameter
+// ordering rule, reimplemented from the former custom TSLint rule
+// (ZEPPELIN-6301 / ZEPPELIN-6372). The alphabetical public-api.ts export sort
+// (ZEPPELIN-6325) is delegated to eslint-plugin-perfectionist instead.
+
+'use strict';
+
+module.exports = {
+ rules: {
+ 'constructor-params-order': require('./constructor-params-order')
+ }
+};
diff --git a/zeppelin-web-angular/eslint.config.js
b/zeppelin-web-angular/eslint.config.js
index 69bc491e05..ba289b6519 100644
--- a/zeppelin-web-angular/eslint.config.js
+++ b/zeppelin-web-angular/eslint.config.js
@@ -19,6 +19,10 @@ const importPlugin = require('eslint-plugin-import');
const jsdoc = require('eslint-plugin-jsdoc');
const preferArrow = require('eslint-plugin-prefer-arrow');
const prettier = require('eslint-config-prettier');
+// Local plugin: the Angular-specific constructor-params-order rule
reimplemented
+// from the former TSLint rule (ZEPPELIN-6372).
+const localRules = require('./eslint-rules');
+const perfectionist = require('eslint-plugin-perfectionist');
module.exports = tseslint.config(
{
@@ -50,7 +54,8 @@ module.exports = tseslint.config(
'@typescript-eslint': tseslint.plugin,
import: importPlugin,
jsdoc,
- 'prefer-arrow': preferArrow
+ 'prefer-arrow': preferArrow,
+ local: localRules
},
rules: {
'@angular-eslint/component-selector': [
@@ -104,6 +109,11 @@ module.exports = tseslint.config(
'@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/member-ordering': 'warn',
+
+ // Custom rule (reimplemented from TSLint, ZEPPELIN-6372): keep the New
UI
+ // constructor parameter order consistent as public -> protected ->
private.
+ 'local/constructor-params-order': 'error',
+
'@typescript-eslint/explicit-member-accessibility': ['off', {
accessibility: 'explicit' }],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-floating-promises': 'off',
@@ -146,6 +156,16 @@ module.exports = tseslint.config(
'@angular-eslint/directive-selector': ['error', { type: 'attribute',
prefix: 'lib', style: 'camelCase' }]
}
},
+ {
+ // ZEPPELIN-6325 / ZEPPELIN-6372: keep public-api.ts barrels alphabetically
+ // ordered by module specifier. Delegated to eslint-plugin-perfectionist
+ // rather than owning a custom statement-reordering fixer.
+ files: ['**/public-api.ts'],
+ plugins: { perfectionist },
+ rules: {
+ 'perfectionist/sort-exports': 'error'
+ }
+ },
{
files: ['**/*.html'],
// == legacy `plugin:@angular-eslint/template/recommended`
diff --git a/zeppelin-web-angular/package-lock.json
b/zeppelin-web-angular/package-lock.json
index d65d629675..9d0ccbc33e 100644
--- a/zeppelin-web-angular/package-lock.json
+++ b/zeppelin-web-angular/package-lock.json
@@ -69,6 +69,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsdoc": "^50.8.0",
+ "eslint-plugin-perfectionist": "^5.10.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"https-proxy-agent": "^2.2.1",
"husky": "9.1.7",
@@ -12390,6 +12391,177 @@
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
+ "node_modules/eslint-plugin-perfectionist": {
+ "version": "5.10.0",
+ "resolved":
"https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.10.0.tgz",
+ "integrity":
"sha512-HiqpDrUDbGrMC6iHQbemgDyHJ0366Vyz/qRWmxQcSAkmG25cXr8BdRgx8yAhOKhEfBXn8Rnf/mTCsV4EqUJSxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/utils": "^8.62.1",
+ "natural-orderby": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.0.0 || >=22.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/project-service":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
+ "integrity":
"sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.63.0",
+ "@typescript-eslint/types": "^8.63.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/scope-manager":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
+ "integrity":
"sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.63.0",
+ "@typescript-eslint/visitor-keys": "8.63.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/tsconfig-utils":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
+ "integrity":
"sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/types":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
+ "integrity":
"sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/typescript-estree":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
+ "integrity":
"sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.63.0",
+ "@typescript-eslint/tsconfig-utils": "8.63.0",
+ "@typescript-eslint/types": "8.63.0",
+ "@typescript-eslint/visitor-keys": "8.63.0",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/utils":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
+ "integrity":
"sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.63.0",
+ "@typescript-eslint/types": "8.63.0",
+ "@typescript-eslint/typescript-estree": "8.63.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/visitor-keys":
{
+ "version": "8.63.0",
+ "resolved":
"https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
+ "integrity":
"sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.63.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+
"node_modules/eslint-plugin-perfectionist/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved":
"https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity":
"sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/eslint-plugin-prefer-arrow": {
"version": "1.2.3",
"resolved":
"https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.3.tgz",
@@ -16236,6 +16408,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/natural-orderby": {
+ "version": "5.0.0",
+ "resolved":
"https://registry.npmjs.org/natural-orderby/-/natural-orderby-5.0.0.tgz",
+ "integrity":
"sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/needle": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz",
diff --git a/zeppelin-web-angular/package.json
b/zeppelin-web-angular/package.json
index aafb74bdc2..5e84360608 100644
--- a/zeppelin-web-angular/package.json
+++ b/zeppelin-web-angular/package.json
@@ -18,6 +18,7 @@
"lint:fix": "cross-env NODE_OPTIONS='--max-old-space-size=8192' ng lint
--fix && npm run lint:fix:react && prettier --write
\"**/*.{ts,js,json,css,html}\"",
"lint:react": "cd projects/zeppelin-react && npm run lint",
"lint:fix:react": "cd projects/zeppelin-react && npm run lint:fix",
+ "test:eslint-rules": "node --test eslint-rules/",
"e2e": "playwright test",
"e2e:fast": "playwright test --project=chromium",
"e2e:ui": "playwright test --ui",
@@ -93,6 +94,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsdoc": "^50.8.0",
+ "eslint-plugin-perfectionist": "^5.10.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
"https-proxy-agent": "^2.2.1",
"husky": "9.1.7",