1 | import * as Lint from "tslint";
|
2 | import * as ts from "typescript";
|
3 |
|
4 | import { ConfigFactory } from "./config/ConfigFactory";
|
5 | import { GeneralRuleUtils } from "./utils/GeneralRuleUtils";
|
6 | import { ImportRuleUtils, PathSource } from "./utils/ImportRuleUtils";
|
7 |
|
8 | export const IMPORTS_BETWEEN_PACKAGES_RULE_ID =
|
9 | "tsf-folders-imports-between-packages";
|
10 |
|
11 | const DISALLOW_IMPORT_FROM_SELF_MESSAGE =
|
12 | "do not import a package from itself - use a relative path";
|
13 |
|
14 | export class Rule extends Lint.Rules.AbstractRule {
|
15 | apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
16 | const walker = new ImportsWalker(sourceFile, this.getOptions());
|
17 | this.applyWithWalker(walker);
|
18 |
|
19 | return walker.getFailures();
|
20 | }
|
21 | }
|
22 |
|
23 | class ImportsWalker extends Lint.RuleWalker {
|
24 | visitImportDeclaration(node: ts.ImportDeclaration) {
|
25 | this.validate(node, node.moduleSpecifier.getText());
|
26 | }
|
27 |
|
28 | visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration) {
|
29 | this.validate(node, node.moduleReference.getText());
|
30 |
|
31 | super.visitImportEqualsDeclaration(node);
|
32 | }
|
33 |
|
34 | private validate(node: ts.Node, text: string) {
|
35 |
|
36 | |
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | const config = ConfigFactory.createForBetweenPackages(this.getOptions());
|
46 |
|
47 | const thisPackageLocation = ImportRuleUtils.determinePackageLocationFromPath(
|
48 | node.getSourceFile().fileName,
|
49 | IMPORTS_BETWEEN_PACKAGES_RULE_ID,
|
50 | config,
|
51 | PathSource.SourceFilePath
|
52 | );
|
53 |
|
54 | if (ImportRuleUtils.isThisPackageThirdParty(thisPackageLocation, node)) {
|
55 | return;
|
56 | }
|
57 |
|
58 | if (!thisPackageLocation.packageFolder) {
|
59 | throw new Error(
|
60 | "unexpected: this package is not ThirdParty, but has no packageFolder in its location"
|
61 | );
|
62 | }
|
63 |
|
64 | const importPackageLocation = ImportRuleUtils.determinePackageLocationFromPath(
|
65 | text,
|
66 | IMPORTS_BETWEEN_PACKAGES_RULE_ID,
|
67 | config,
|
68 | PathSource.ImportText,
|
69 | thisPackageLocation
|
70 | );
|
71 |
|
72 | ImportRuleUtils.logPackageAndImport(
|
73 | node,
|
74 | thisPackageLocation,
|
75 | importPackageLocation
|
76 | );
|
77 |
|
78 | const isImportRecognised = !ImportRuleUtils.isPackageThirdParty(
|
79 | importPackageLocation
|
80 | );
|
81 |
|
82 | if (
|
83 | isImportRecognised &&
|
84 | importPackageLocation.packageName === thisPackageLocation.packageName &&
|
85 | config.disallowImportFromSelf.enabled &&
|
86 | !ImportRuleUtils.shouldIgnoreFile(
|
87 | node,
|
88 | config.disallowImportFromSelf.ignorePaths
|
89 | )
|
90 | ) {
|
91 | this.addFailureAtNode(
|
92 | node,
|
93 | GeneralRuleUtils.buildFailureString(
|
94 | DISALLOW_IMPORT_FROM_SELF_MESSAGE,
|
95 | IMPORTS_BETWEEN_PACKAGES_RULE_ID
|
96 | )
|
97 | );
|
98 | return;
|
99 | }
|
100 |
|
101 | if (
|
102 | isImportRecognised &&
|
103 | config.checkImportsBetweenPackages.enabled &&
|
104 | !ImportRuleUtils.shouldIgnoreFile(
|
105 | node,
|
106 | config.checkImportsBetweenPackages.ignorePaths
|
107 | )
|
108 | ) {
|
109 | if (
|
110 | !thisPackageLocation.packageFolder ||
|
111 | !importPackageLocation.packageFolder
|
112 | ) {
|
113 | return;
|
114 | }
|
115 |
|
116 | if (
|
117 | importPackageLocation.packageFolder ===
|
118 | thisPackageLocation.packageFolder
|
119 | ) {
|
120 | if (!config.checkImportsBetweenPackages.checkSubFoldersEnabled) {
|
121 | return;
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 | if (
|
128 | thisPackageLocation.packageSubFolder &&
|
129 | importPackageLocation.packageSubFolder &&
|
130 | thisPackageLocation.packageSubFolder.importPath !==
|
131 | importPackageLocation.packageSubFolder.importPath
|
132 | ) {
|
133 | if (
|
134 | thisPackageLocation.packageSubFolder.allowedToImport.some(
|
135 | allowed => allowed === "*"
|
136 | )
|
137 | ) {
|
138 | return;
|
139 | }
|
140 |
|
141 | if (
|
142 | !thisPackageLocation.packageSubFolder.allowedToImport.some(
|
143 | allowed => {
|
144 | return (
|
145 | importPackageLocation.packageSubFolder!.importPath === allowed
|
146 | );
|
147 | }
|
148 | )
|
149 | ) {
|
150 | const failureMessage = `'${
|
151 | thisPackageLocation.packageName
|
152 | }' sub folder '${
|
153 | thisPackageLocation.packageSubFolder.importPath
|
154 | }' is not allowed to import from '${
|
155 | importPackageLocation.packageSubFolder.importPath
|
156 | }'`;
|
157 |
|
158 | this.addFailureAtNodeWithMessage(node, failureMessage);
|
159 | }
|
160 | }
|
161 | } else {
|
162 | const thisPackageFolder = thisPackageLocation.packageFolder;
|
163 | if (
|
164 | thisPackageFolder.allowedToImport.find(allowed => allowed === "*")
|
165 | ) {
|
166 | return;
|
167 | }
|
168 |
|
169 | if (
|
170 | !thisPackageFolder.allowedToImport.find(
|
171 | allowed => allowed === importPackageLocation.packageName
|
172 | )
|
173 | ) {
|
174 | const failureMessage = `'${
|
175 | thisPackageLocation.packageName
|
176 | }' is not allowed to import from '${
|
177 | importPackageLocation.packageName
|
178 | }'`;
|
179 |
|
180 | this.addFailureAtNodeWithMessage(node, failureMessage);
|
181 | }
|
182 | }
|
183 | }
|
184 | }
|
185 |
|
186 | private addFailureAtNodeWithMessage(node: ts.Node, failureMessage: string) {
|
187 | this.addFailureAtNode(
|
188 | node,
|
189 | GeneralRuleUtils.buildFailureString(
|
190 | failureMessage,
|
191 | IMPORTS_BETWEEN_PACKAGES_RULE_ID
|
192 | )
|
193 | );
|
194 | }
|
195 | }
|