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",

Reply via email to