Skip to content

Commit 5d9fe3b

Browse files
committed
Break import handling into its own abstraction
1 parent a51fa0e commit 5d9fe3b

File tree

3 files changed

+192
-54
lines changed

3 files changed

+192
-54
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.typescript.codegen;
17+
18+
import java.nio.file.Path;
19+
import java.nio.file.Paths;
20+
import java.util.Map;
21+
import java.util.TreeMap;
22+
23+
/**
24+
* Internal class used for aggregating imports of a file.
25+
*/
26+
final class ImportDeclarations {
27+
28+
private final Path relativize;
29+
private final Map<String, Map<String, String>> imports = new TreeMap<>();
30+
31+
ImportDeclarations(String relativize) {
32+
if (!relativize.startsWith("./")) {
33+
relativize = "./" + relativize;
34+
}
35+
36+
this.relativize = Paths.get(relativize);
37+
}
38+
39+
ImportDeclarations addImport(String name, String alias, String module) {
40+
if (alias == null || alias.isEmpty()) {
41+
alias = name;
42+
}
43+
44+
if (relativize != null && module.startsWith(".")) {
45+
// A relative import is resolved against the current file.
46+
module = relativize.relativize(Paths.get(module)).toString();
47+
if (!module.startsWith(".")) {
48+
module = "./" + module;
49+
}
50+
}
51+
52+
if (!module.isEmpty() && (relativize == null || !module.equals(relativize.toString()))) {
53+
imports.computeIfAbsent(module, m -> new TreeMap<>()).put(alias, name);
54+
}
55+
56+
return this;
57+
}
58+
59+
@Override
60+
public String toString() {
61+
StringBuilder result = new StringBuilder();
62+
63+
if (!imports.isEmpty()) {
64+
for (Map.Entry<String, Map<String, String>> entry : imports.entrySet()) {
65+
String module = entry.getKey();
66+
Map<String, String> imports = entry.getValue();
67+
68+
if (imports.size() == 1) {
69+
Map.Entry<String, String> singleEntry = imports.entrySet().iterator().next();
70+
result.append("import { ")
71+
.append(createImportStatement(singleEntry))
72+
.append(" } from \"")
73+
.append(module)
74+
.append("\";\n");
75+
} else {
76+
result.append("import {\n");
77+
for (Map.Entry<String, String> importEntry : imports.entrySet()) {
78+
result.append(" ");
79+
result.append(createImportStatement(importEntry));
80+
result.append(",\n");
81+
}
82+
result.append("} from \"").append(module).append("\";\n");
83+
}
84+
}
85+
result.append("\n");
86+
}
87+
88+
return result.toString();
89+
}
90+
91+
private static String createImportStatement(Map.Entry<String, String> entry) {
92+
return entry.getKey().equals(entry.getValue())
93+
? entry.getKey()
94+
: entry.getValue() + " as " + entry.getKey();
95+
}
96+
}

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/TypeScriptWriter.java

Lines changed: 22 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
import java.nio.file.Path;
1919
import java.nio.file.Paths;
20-
import java.util.Map;
21-
import java.util.TreeMap;
2220
import java.util.function.BiFunction;
2321
import software.amazon.smithy.codegen.core.CodegenException;
2422
import software.amazon.smithy.codegen.core.Symbol;
@@ -39,14 +37,17 @@
3937
* module paths against the moduleName of the writer. Module names that
4038
* start with anything other than "." (e.g., "@", "/", etc.) are never
4139
* relativized.
40+
*
41+
* TODO: Make this public when it's stable.
4242
*/
4343
final class TypeScriptWriter extends CodeWriter {
4444

4545
private final Path moduleName;
46-
private final Map<String, Map<String, String>> imports = new TreeMap<>();
46+
private final ImportDeclarations imports;
4747

4848
TypeScriptWriter(String moduleName) {
4949
this.moduleName = Paths.get(moduleName);
50+
imports = new ImportDeclarations(moduleName);
5051

5152
setIndentText(" ");
5253
trimTrailingSpaces(true);
@@ -62,21 +63,30 @@ final class TypeScriptWriter extends CodeWriter {
6263
*/
6364
TypeScriptWriter addImport(Symbol symbol) {
6465
if (!symbol.getNamespace().isEmpty()) {
65-
addImport(symbol.getName(), symbol.getNamespace());
66+
addImport(symbol.getName(), symbol.getName(), symbol.getNamespace());
67+
for (SymbolReference reference : symbol.getReferences()) {
68+
addImport(reference);
69+
}
6670
}
6771

6872
return this;
6973
}
7074

7175
/**
72-
* Imports a type from a module only if necessary.
76+
* Imports a symbol reference.
7377
*
74-
* @param name Type to import.
75-
* @param from Module to import the type from.
78+
* @param reference Symbol reference to import.
7679
* @return Returns the writer.
7780
*/
78-
TypeScriptWriter addImport(String name, String from) {
79-
return addImport(name, name, from);
81+
TypeScriptWriter addImport(SymbolReference reference) {
82+
// If there's no alias, then just import the symbol normally.
83+
if (reference.getAlias().equals(reference.getSymbol().getName())) {
84+
return addImport(reference.getSymbol());
85+
}
86+
87+
// Symbols with references are always imported since they don't
88+
// conflict and must be imported in order for the code to work.
89+
return addImport(reference.getSymbol().getName(), reference.getAlias(), reference.getSymbol().getNamespace());
8090
}
8191

8292
/**
@@ -88,15 +98,7 @@ TypeScriptWriter addImport(String name, String from) {
8898
* @return Returns the writer.
8999
*/
90100
TypeScriptWriter addImport(String name, String as, String from) {
91-
if (!from.startsWith("@") && !from.startsWith("/")) {
92-
// A relative import is resolved against the current file.
93-
from = moduleName.relativize(Paths.get(from)).toString();
94-
}
95-
96-
if (!from.equals(moduleName.toString()) && !from.isEmpty()) {
97-
imports.computeIfAbsent(from, m -> new TreeMap<>()).put(as, name);
98-
}
99-
101+
imports.addImport(name, as, from);
100102
return this;
101103
}
102104

@@ -161,41 +163,7 @@ boolean writeMemberDocs(Model model, MemberShape member) {
161163

162164
@Override
163165
public String toString() {
164-
StringBuilder result = new StringBuilder();
165-
166-
if (!imports.isEmpty()) {
167-
for (Map.Entry<String, Map<String, String>> entry : imports.entrySet()) {
168-
String module = entry.getKey();
169-
Map<String, String> imports = entry.getValue();
170-
171-
if (imports.size() == 1) {
172-
Map.Entry<String, String> singleEntry = imports.entrySet().iterator().next();
173-
result.append("import { ")
174-
.append(createImportStatement(singleEntry))
175-
.append(" } from \"")
176-
.append(module)
177-
.append("\";\n");
178-
} else {
179-
result.append("import {\n");
180-
for (Map.Entry<String, String> importEntry : imports.entrySet()) {
181-
result.append(" ");
182-
result.append(createImportStatement(importEntry));
183-
result.append(",\n");
184-
}
185-
result.append("} from \"").append(module).append("\";\n");
186-
}
187-
}
188-
result.append("\n");
189-
}
190-
191-
result.append(super.toString());
192-
return result.toString();
193-
}
194-
195-
private String createImportStatement(Map.Entry<String, String> entry) {
196-
return entry.getKey().equals(entry.getValue())
197-
? entry.getKey()
198-
: entry.getValue() + " as " + entry.getKey();
166+
return imports.toString() + super.toString();
199167
}
200168

201169
/**
@@ -213,7 +181,7 @@ public String apply(Object type, String indent) {
213181

214182
for (SymbolReference reference : typeSymbol.getReferences()) {
215183
if (reference.hasOption(SymbolReference.ContextOption.USE)) {
216-
addImport(reference.getSymbol());
184+
addImport(reference);
217185
}
218186
}
219187

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package software.amazon.smithy.typescript.codegen;
2+
3+
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.Matchers.containsString;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
public class ImportDeclarationsTest {
9+
@Test
10+
public void addsSingleNonAliasedImport() {
11+
ImportDeclarations declarations = new ImportDeclarations("foo/bar");
12+
declarations.addImport("Big", "", "big.js");
13+
String result = declarations.toString();
14+
15+
assertThat(result, containsString("import { Big } from \"big.js\";"));
16+
}
17+
18+
@Test
19+
public void addsSingleAliasedImport() {
20+
ImportDeclarations declarations = new ImportDeclarations("foo/bar");
21+
declarations.addImport("Big", "$Big", "big.js");
22+
String result = declarations.toString();
23+
24+
assertThat(result, containsString("import { Big as $Big } from \"big.js\";"));
25+
}
26+
27+
@Test
28+
public void addsMultipleImportsOfSameSymbol() {
29+
ImportDeclarations declarations = new ImportDeclarations("foo/bar");
30+
declarations.addImport("Big", "Big", "big.js");
31+
declarations.addImport("Big", "$Big", "big.js");
32+
String result = declarations.toString();
33+
34+
assertThat(result, containsString("import {\n Big as $Big,\n Big,\n} from \"big.js\";"));
35+
}
36+
37+
@Test
38+
public void relativizesImports() {
39+
ImportDeclarations declarations = new ImportDeclarations("./foo/bar");
40+
declarations.addImport("Baz", "", "./foo/bar/bam");
41+
String result = declarations.toString();
42+
43+
assertThat(result, containsString("import { Baz } from \"./bam\";"));
44+
}
45+
46+
@Test
47+
public void automaticallyCorrectsBasePath() {
48+
ImportDeclarations declarations = new ImportDeclarations("/foo/bar");
49+
declarations.addImport("Baz", "", "./foo/bar/bam/qux");
50+
String result = declarations.toString();
51+
52+
assertThat(result, containsString("import { Baz } from \"./bam/qux\";"));
53+
}
54+
55+
@Test
56+
public void doesNotRelativizeAbsolutePaths() {
57+
ImportDeclarations declarations = new ImportDeclarations("/foo/bar");
58+
declarations.addImport("Baz", "", "@types/foo");
59+
declarations.addImport("Hello", "", "/abc/def");
60+
String result = declarations.toString();
61+
62+
assertThat(result, containsString("import { Baz } from \"@types/foo\";"));
63+
assertThat(result, containsString("import { Hello } from \"/abc/def\";"));
64+
}
65+
66+
@Test
67+
public void canImportFilesUpLevels() {
68+
ImportDeclarations declarations = new ImportDeclarations("/foo/bar");
69+
declarations.addImport("SharedThing", "", "./shared/types");
70+
String result = declarations.toString();
71+
72+
assertThat(result, containsString("import { SharedThing } from \"../../shared/types\";"));
73+
}
74+
}

0 commit comments

Comments
 (0)