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"]]
}
}