Skip to content

Commit f64cd41

Browse files
committed
feat: attach module documentation to the lookup item
1 parent be9a1ba commit f64cd41

File tree

4 files changed

+131
-53
lines changed

4 files changed

+131
-53
lines changed

build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ dependencies {
1919
intellij {
2020
version 'IU-2019.2'
2121
downloadSources true
22-
plugins = ['com.github.lppedd.idea-conventional-commit:0.2.0', 'AngularJS']
22+
plugins = [
23+
'com.github.lppedd.idea-conventional-commit:0.2.0',
24+
'JavaScriptLanguage',
25+
'AngularJS'
26+
]
2327
}
2428

2529
patchPluginXml {

src/main/java/com/github/lppedd/cc/angular2/Angular2CommitScopeProvider.java

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
11
package com.github.lppedd.cc.angular2;
22

3+
import java.util.ArrayList;
34
import java.util.Arrays;
45
import java.util.Collection;
5-
import java.util.HashSet;
6+
import java.util.Comparator;
67
import java.util.List;
78
import java.util.stream.Collectors;
89

9-
import org.angular2.lang.Angular2LangUtil;
10+
import org.angular2.entities.Angular2EntitiesProvider;
11+
import org.angular2.entities.Angular2Entity;
12+
import org.angular2.entities.Angular2Module;
13+
import org.angular2.index.Angular2IndexingHandler;
14+
import org.angular2.index.Angular2SourceModuleIndex;
1015
import org.jetbrains.annotations.NotNull;
1116
import org.jetbrains.annotations.Nullable;
1217

1318
import com.github.lppedd.cc.api.CommitScopeProvider;
1419
import com.github.lppedd.cc.api.ProviderPresentation;
20+
import com.intellij.codeInsight.documentation.DocumentationManager;
21+
import com.intellij.lang.javascript.psi.JSImplicitElementProvider;
1522
import com.intellij.openapi.application.Application;
1623
import com.intellij.openapi.application.ApplicationManager;
1724
import com.intellij.openapi.project.Project;
1825
import com.intellij.openapi.util.Computable;
19-
import com.intellij.openapi.vfs.VirtualFile;
20-
import com.intellij.psi.search.FilenameIndex;
26+
import com.intellij.psi.PsiElement;
2127
import com.intellij.psi.search.GlobalSearchScope;
22-
import com.intellij.util.Processor;
23-
import com.intellij.util.indexing.IdFilter;
28+
import com.intellij.psi.stubs.StubIndex;
29+
import com.intellij.psi.util.CachedValueProvider.Result;
30+
import com.intellij.psi.util.CachedValuesManager;
31+
import com.intellij.psi.util.PsiModificationTracker;
32+
import com.intellij.util.containers.ContainerUtil;
2433

2534
/**
2635
* @author Edoardo Luppi
@@ -33,10 +42,14 @@ class Angular2CommitScopeProvider implements CommitScopeProvider {
3342
);
3443

3544
private static final Application APPLICATION = ApplicationManager.getApplication();
45+
private static final StubIndex STUB_INDEX = StubIndex.getInstance();
46+
3647
private final Project project;
48+
private final CachedValuesManager cachedValuesManager;
3749

38-
Angular2CommitScopeProvider(@NotNull final Project project) {
50+
Angular2CommitScopeProvider(final Project project) {
3951
this.project = project;
52+
cachedValuesManager = CachedValuesManager.getManager(project);
4053
}
4154

4255
@NotNull
@@ -54,56 +67,61 @@ public ProviderPresentation getPresentation() {
5467
@NotNull
5568
@Override
5669
public List<CommitScope> getCommitScopes(@Nullable final String commitType) {
57-
return "build".equals(commitType) ? SCOPES : findNgModules();
70+
return "build".equals(commitType) ? SCOPES : findModules();
71+
}
72+
73+
@NotNull
74+
private List<CommitScope> findModules() {
75+
return cachedValuesManager.getCachedValue(project, this::computeCommitScopes);
5876
}
5977

6078
@NotNull
61-
private List<CommitScope> findNgModules() {
62-
final Collection<String> fileNames = new HashSet<>(64);
63-
64-
final Processor<String> stringProcessor = fileName -> {
65-
if (fileName.toLowerCase().endsWith(".module.ts")) {
66-
fileNames.add(fileName);
67-
}
68-
69-
return true;
70-
};
71-
72-
final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(project);
73-
74-
APPLICATION.runReadAction(() ->
75-
FilenameIndex.processAllFileNames(
76-
stringProcessor,
77-
projectScope,
78-
IdFilter.getProjectIdFilter(project, false)
79-
)
80-
);
81-
82-
return fileNames
83-
.stream()
84-
.flatMap(fileName ->
85-
APPLICATION.runReadAction((Computable<Collection<VirtualFile>>) () ->
86-
FilenameIndex.getVirtualFilesByName(
87-
project,
88-
fileName,
89-
true,
90-
projectScope
91-
)
92-
).stream()
93-
)
94-
.filter(this::isAngular2Context)
95-
.map(VirtualFile::getName)
96-
.map(String::toLowerCase)
97-
.map(fileName -> fileName.replaceFirst(".module.ts$", ""))
98-
.sorted()
99-
.map(moduleName -> new Angular2CommitScope(moduleName, null))
100-
.collect(Collectors.toList());
79+
private Result<List<CommitScope>> computeCommitScopes() {
80+
final List<CommitScope> commitScopes =
81+
APPLICATION.runReadAction((Computable<List<CommitScope>>) () ->
82+
findSourceModules()
83+
.stream()
84+
.map(this::toCommitScope)
85+
.sorted(Comparator.comparing(CommitScope::getText))
86+
.collect(Collectors.toList())
87+
);
88+
89+
return Result.create(commitScopes, PsiModificationTracker.MODIFICATION_COUNT);
10190
}
10291

103-
private boolean isAngular2Context(@NotNull final VirtualFile virtualFile) {
104-
return APPLICATION.runReadAction(
105-
(Computable<Boolean>) () -> Angular2LangUtil.isAngular2Context(project, virtualFile)
106-
);
92+
/** Returns the local project's {@code NgModule}s, without externally provided ones. */
93+
@NotNull
94+
private Collection<Angular2Module> findSourceModules() {
95+
final Collection<Angular2Module> modules = new ArrayList<>(32);
96+
STUB_INDEX.processElements(
97+
Angular2SourceModuleIndex.KEY,
98+
Angular2IndexingHandler.NG_MODULE_INDEX_NAME,
99+
project,
100+
GlobalSearchScope.projectScope(project),
101+
JSImplicitElementProvider.class,
102+
module -> {
103+
if (module.isValid()) {
104+
ContainerUtil.addIfNotNull(modules, Angular2EntitiesProvider.getModule(module));
105+
}
106+
107+
return true;
108+
});
109+
110+
return modules;
111+
}
112+
113+
/**
114+
* Transforms a {@code NgModule} definition to a {@link CommitScope}.<br />
115+
* If a JSDoc comment is found, it is used as scope description.
116+
*/
117+
@NotNull
118+
private CommitScope toCommitScope(@NotNull final Angular2Entity module) {
119+
final PsiElement sourceElement = module.getTypeScriptClass();
120+
final String name = CCAngularUtils.toDashCase(module.getName()).replaceFirst("-module$", "");
121+
final String documentation =
122+
DocumentationManager.getProviderFromElement(sourceElement)
123+
.generateDoc(sourceElement, null);
124+
return new Angular2CommitScope(name, documentation);
107125
}
108126

109127
private static class Angular2CommitBuildScope extends CommitScope {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.github.lppedd.cc.angular2;
2+
3+
import java.util.regex.Pattern;
4+
5+
import org.jetbrains.annotations.NotNull;
6+
7+
/**
8+
* @author Edoardo Luppi
9+
*/
10+
final class CCAngularUtils {
11+
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("[^>](?=[A-Z][a-z])");
12+
13+
@NotNull
14+
static String toDashCase(@NotNull final String str) {
15+
if (str.isEmpty()) {
16+
throw new IllegalArgumentException("Input string cannot be empty");
17+
}
18+
19+
return CAMEL_CASE_PATTERN.matcher(str)
20+
.replaceAll("$0-")
21+
.toLowerCase();
22+
}
23+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.github.lppedd.cc.angular2;
2+
3+
import static junit.framework.TestCase.assertEquals;
4+
import static junit.framework.TestCase.assertSame;
5+
import static junit.framework.TestCase.fail;
6+
7+
import org.junit.Test;
8+
9+
/**
10+
* @author Edoardo Luppi
11+
*/
12+
public class CCAngularUtilsTest {
13+
@Test
14+
public void shouldConvertCamelToDashCase() {
15+
assertEquals("example-one-module", CCAngularUtils.toDashCase("example-one-module"));
16+
assertEquals("example-one-module", CCAngularUtils.toDashCase("exampleOneModule"));
17+
assertEquals("example-one-module", CCAngularUtils.toDashCase("ExampleOneModule"));
18+
assertEquals("12-example-two-module", CCAngularUtils.toDashCase("12ExampleTwoModule"));
19+
assertEquals("12-example12-two-module", CCAngularUtils.toDashCase("12Example12TwoModule"));
20+
assertEquals("12-example12two-module", CCAngularUtils.toDashCase("12Example12TWOModule"));
21+
}
22+
23+
@Test
24+
public void shouldThrowExceptionForEmptyString() {
25+
try {
26+
CCAngularUtils.toDashCase("");
27+
fail();
28+
} catch (final Exception e) {
29+
assertSame(e.getClass(), IllegalArgumentException.class);
30+
assertEquals("Input string cannot be empty", e.getMessage());
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)