Skip to content

Commit 18779ff

Browse files
dididytbonelee
authored andcommitted
[ZEPPELIN-6325] Add Custom TSLint Rule to Alphabetically Order Exports in public-api.ts
### What is this PR for? #5065 (comment) 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]> (cherry picked from commit 8d18618) Signed-off-by: ChanHo Lee <[email protected]>
1 parent a57205e commit 18779ff

File tree

4 files changed

+136
-4
lines changed

4 files changed

+136
-4
lines changed

zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@
1414
* Public API Surface of zeppelin-helium
1515
*/
1616

17-
export * from './zeppelin-helium.service';
1817
export * from './zeppelin-helium.module';
18+
export * from './zeppelin-helium.service';

zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@
1010
* limitations under the License.
1111
*/
1212

13-
export * from './message';
14-
// https://github.com/ng-packagr/ng-packagr/issues/1093
1513
export * from './interfaces/public-api';
14+
export * from './message';
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
* http://www.apache.org/licenses/LICENSE-2.0
6+
* Unless required by applicable law or agreed to in writing,
7+
* distributed under the License is distributed on an "AS IS" BASIS,
8+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
* See the License for the specific language governing permissions and
10+
* limitations under the License.
11+
*/
12+
13+
import * as Lint from 'tslint';
14+
import * as ts from 'typescript';
15+
16+
interface OptionsType {
17+
targetFiles: string[];
18+
}
19+
20+
export class Rule extends Lint.Rules.AbstractRule {
21+
public static FAILURE_STRING = 'Export statements should be alphabetically ordered by module specifier';
22+
23+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
24+
const targetFiles =
25+
this.ruleArguments.length > 0 && Array.isArray(this.ruleArguments[0]) ? this.ruleArguments[0] : [];
26+
return this.applyWithFunction(sourceFile, walk, { targetFiles });
27+
}
28+
}
29+
30+
function walk(ctx: Lint.WalkContext<OptionsType>) {
31+
const targetFiles = ctx.options.targetFiles || [];
32+
const fileName = ctx.sourceFile.fileName;
33+
34+
const shouldApply = targetFiles.some(targetFile => fileName.endsWith(targetFile));
35+
36+
if (!shouldApply) {
37+
return;
38+
}
39+
40+
const exportStatements: Array<{
41+
node: ts.Node;
42+
sortKey: string;
43+
start: number;
44+
end: number;
45+
}> = [];
46+
47+
const visit = (node: ts.Node) => {
48+
if (isExportStatement(node)) {
49+
const sortKey = getSortKey(node);
50+
if (sortKey) {
51+
exportStatements.push({
52+
node,
53+
sortKey,
54+
start: node.getStart(),
55+
end: node.getEnd()
56+
});
57+
}
58+
}
59+
ts.forEachChild(node, visit);
60+
};
61+
62+
visit(ctx.sourceFile);
63+
64+
if (exportStatements.length <= 1) {
65+
return;
66+
}
67+
68+
const isSorted = exportStatements.every(
69+
(statement, i, arr) =>
70+
i === 0 ||
71+
statement.sortKey.localeCompare(arr[i - 1].sortKey, undefined, {
72+
sensitivity: 'base'
73+
}) >= 0
74+
);
75+
76+
if (!isSorted) {
77+
const sortedExports = [...exportStatements].sort((a, b) =>
78+
a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' })
79+
);
80+
81+
const sourceText = ctx.sourceFile.text;
82+
const fixText = sortedExports
83+
.map(exportStatement => sourceText.substring(exportStatement.start, exportStatement.end).trim())
84+
.join('\n');
85+
86+
const firstExport = exportStatements[0];
87+
const lastExport = exportStatements[exportStatements.length - 1];
88+
89+
const fix = Lint.Replacement.replaceFromTo(firstExport.start, lastExport.end, fixText);
90+
91+
ctx.addFailureAtNode(firstExport.node, Rule.FAILURE_STRING, fix);
92+
}
93+
}
94+
95+
function hasExportModifier(node: ts.Node): boolean {
96+
return !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
97+
}
98+
99+
function isExportStatement(node: ts.Node): boolean {
100+
return ts.isExportDeclaration(node) || ts.isExportAssignment(node) || hasExportModifier(node);
101+
}
102+
103+
function getSortKey(node: ts.Node): string | null {
104+
if (ts.isExportDeclaration(node)) {
105+
if (node.moduleSpecifier) {
106+
return node.moduleSpecifier.getText().replace(/['"]/g, '');
107+
} else if (node.exportClause && ts.isNamedExports(node.exportClause)) {
108+
return node.exportClause.elements.map(el => el.name.getText()).join(', ');
109+
}
110+
}
111+
112+
if (ts.isExportAssignment(node)) {
113+
return 'export-assignment';
114+
}
115+
116+
if (ts.isVariableStatement(node)) {
117+
return node.declarationList.declarations[0]?.name.getText() ?? null;
118+
}
119+
120+
if (
121+
(ts.isFunctionDeclaration(node) ||
122+
ts.isClassDeclaration(node) ||
123+
ts.isInterfaceDeclaration(node) ||
124+
ts.isTypeAliasDeclaration(node) ||
125+
ts.isEnumDeclaration(node)) &&
126+
node.name
127+
) {
128+
return node.name.getText();
129+
}
130+
131+
return null;
132+
}

zeppelin-web-angular/tslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
"variable-name": [true, "ban-keywords", "allow-leading-underscore"],
143143
"whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
144144
"no-input-rename": true,
145-
"constructor-params-order": true
145+
"constructor-params-order": true,
146+
"ordered-exports": [true, ["public-api.ts"]]
146147
}
147148
}

0 commit comments

Comments
 (0)