This is an automated email from the ASF dual-hosted git repository.

chanholee 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 8d18618eab [ZEPPELIN-6325] Add Custom TSLint Rule to Alphabetically 
Order Exports in public-api.ts
8d18618eab is described below

commit 8d18618eabbcc5ab6e27f30363712207445f499b
Author: YONGJAE LEE(이용재) <[email protected]>
AuthorDate: Thu Sep 18 11:45:17 2025 +0900

    [ZEPPELIN-6325] Add Custom TSLint Rule to Alphabetically Order Exports in 
public-api.ts
    
    ### What is this PR for?
    https://github.com/apache/zeppelin/pull/5065#issuecomment-3266268119
    
    In #5065, it was suggested that keeping exports sorted alphabetically would 
improve readability and consistency.
    
    TSLint does not provide a built-in rule for this (only `ordered-imports` 
exists for imports). Therefore, following the approach from #5053, I added a 
custom TSLint rule to enforce alphabetical ordering for exports.
    
    Applying this rule across the entire codebase would introduce many 
disruptive changes, so for now it is scoped only to the **public-api.ts** file.
    
    
    ### What type of PR is it?
    Improvement
    
    ### Todos
    
    ### What is the Jira issue?
    * [[ZEPPELIN-6325](https://issues.apache.org/jira/browse/ZEPPELIN-6325)]
    
    ### How should this be tested?
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the license files need to update? N
    * Is there breaking changes for older versions? N
    * Does this needs documentation? N
    
    
    Closes #5071 from dididy/test/orderedExports.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../projects/zeppelin-helium/src/public-api.ts     |   2 +-
 .../projects/zeppelin-sdk/src/public-api.ts        |   3 +-
 .../tslint-rules/orderedExportsRule.ts             | 132 +++++++++++++++++++++
 zeppelin-web-angular/tslint.json                   |   3 +-
 4 files changed, 136 insertions(+), 4 deletions(-)

diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts 
b/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts
index 4b9b89fd7d..afa36348a5 100644
--- a/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts
@@ -14,5 +14,5 @@
  * Public API Surface of zeppelin-helium
  */
 
-export * from './zeppelin-helium.service';
 export * from './zeppelin-helium.module';
+export * from './zeppelin-helium.service';
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
index c541787300..5e6b79271a 100644
--- a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
@@ -10,6 +10,5 @@
  * limitations under the License.
  */
 
-export * from './message';
-// https://github.com/ng-packagr/ng-packagr/issues/1093
 export * from './interfaces/public-api';
+export * from './message';
diff --git a/zeppelin-web-angular/tslint-rules/orderedExportsRule.ts 
b/zeppelin-web-angular/tslint-rules/orderedExportsRule.ts
new file mode 100644
index 0000000000..0959fc526a
--- /dev/null
+++ b/zeppelin-web-angular/tslint-rules/orderedExportsRule.ts
@@ -0,0 +1,132 @@
+/*
+ * 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,
+ * 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.
+ */
+
+import * as Lint from 'tslint';
+import * as ts from 'typescript';
+
+interface OptionsType {
+  targetFiles: string[];
+}
+
+export class Rule extends Lint.Rules.AbstractRule {
+  public static FAILURE_STRING = 'Export statements should be alphabetically 
ordered by module specifier';
+
+  public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
+    const targetFiles =
+      this.ruleArguments.length > 0 && Array.isArray(this.ruleArguments[0]) ? 
this.ruleArguments[0] : [];
+    return this.applyWithFunction(sourceFile, walk, { targetFiles });
+  }
+}
+
+function walk(ctx: Lint.WalkContext<OptionsType>) {
+  const targetFiles = ctx.options.targetFiles || [];
+  const fileName = ctx.sourceFile.fileName;
+
+  const shouldApply = targetFiles.some(targetFile => 
fileName.endsWith(targetFile));
+
+  if (!shouldApply) {
+    return;
+  }
+
+  const exportStatements: Array<{
+    node: ts.Node;
+    sortKey: string;
+    start: number;
+    end: number;
+  }> = [];
+
+  const visit = (node: ts.Node) => {
+    if (isExportStatement(node)) {
+      const sortKey = getSortKey(node);
+      if (sortKey) {
+        exportStatements.push({
+          node,
+          sortKey,
+          start: node.getStart(),
+          end: node.getEnd()
+        });
+      }
+    }
+    ts.forEachChild(node, visit);
+  };
+
+  visit(ctx.sourceFile);
+
+  if (exportStatements.length <= 1) {
+    return;
+  }
+
+  const isSorted = exportStatements.every(
+    (statement, i, arr) =>
+      i === 0 ||
+      statement.sortKey.localeCompare(arr[i - 1].sortKey, undefined, {
+        sensitivity: 'base'
+      }) >= 0
+  );
+
+  if (!isSorted) {
+    const sortedExports = [...exportStatements].sort((a, b) =>
+      a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' })
+    );
+
+    const sourceText = ctx.sourceFile.text;
+    const fixText = sortedExports
+      .map(exportStatement => sourceText.substring(exportStatement.start, 
exportStatement.end).trim())
+      .join('\n');
+
+    const firstExport = exportStatements[0];
+    const lastExport = exportStatements[exportStatements.length - 1];
+
+    const fix = Lint.Replacement.replaceFromTo(firstExport.start, 
lastExport.end, fixText);
+
+    ctx.addFailureAtNode(firstExport.node, Rule.FAILURE_STRING, fix);
+  }
+}
+
+function hasExportModifier(node: ts.Node): boolean {
+  return !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
+}
+
+function isExportStatement(node: ts.Node): boolean {
+  return ts.isExportDeclaration(node) || ts.isExportAssignment(node) || 
hasExportModifier(node);
+}
+
+function getSortKey(node: ts.Node): string | null {
+  if (ts.isExportDeclaration(node)) {
+    if (node.moduleSpecifier) {
+      return node.moduleSpecifier.getText().replace(/['"]/g, '');
+    } else if (node.exportClause && ts.isNamedExports(node.exportClause)) {
+      return node.exportClause.elements.map(el => el.name.getText()).join(', 
');
+    }
+  }
+
+  if (ts.isExportAssignment(node)) {
+    return 'export-assignment';
+  }
+
+  if (ts.isVariableStatement(node)) {
+    return node.declarationList.declarations[0]?.name.getText() ?? null;
+  }
+
+  if (
+    (ts.isFunctionDeclaration(node) ||
+      ts.isClassDeclaration(node) ||
+      ts.isInterfaceDeclaration(node) ||
+      ts.isTypeAliasDeclaration(node) ||
+      ts.isEnumDeclaration(node)) &&
+    node.name
+  ) {
+    return node.name.getText();
+  }
+
+  return null;
+}
diff --git a/zeppelin-web-angular/tslint.json b/zeppelin-web-angular/tslint.json
index ea5c703ecb..685f82671a 100644
--- a/zeppelin-web-angular/tslint.json
+++ b/zeppelin-web-angular/tslint.json
@@ -142,6 +142,7 @@
     "variable-name": [true, "ban-keywords", "allow-leading-underscore"],
     "whitespace": [true, "check-branch", "check-decl", "check-operator", 
"check-separator", "check-type"],
     "no-input-rename": true,
-    "constructor-params-order": true
+    "constructor-params-order": true,
+    "ordered-exports": [true, ["public-api.ts"]]
   }
 }

Reply via email to