1 // Copyright 2000-2018 JetBrains s.r.o.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 @file:JvmName("AngularCliConfigLoader")
16 package org.angularjs.cli
18 import com.google.gson.GsonBuilder
19 import com.google.gson.annotations.Expose
20 import com.google.gson.annotations.SerializedName
21 import com.intellij.openapi.diagnostic.Logger
22 import com.intellij.openapi.fileEditor.FileDocumentManager
23 import com.intellij.openapi.project.Project
24 import com.intellij.openapi.util.Key
25 import com.intellij.openapi.vfs.VfsUtilCore
26 import com.intellij.openapi.vfs.VirtualFile
27 import com.intellij.psi.PsiManager
28 import com.intellij.psi.util.CachedValue
29 import com.intellij.psi.util.CachedValueProvider
30 import com.intellij.psi.util.CachedValuesManager
31 import com.intellij.util.containers.ContainerUtil
32 import com.intellij.util.text.CharSequenceReader
34 private val ANGULAR_CLI_CONFIG_KEY = Key.create<CachedValue<AngularCliConfig>>("ANGULAR_CLI_CONFIG_KEY")
35 private val LOG = Logger.getInstance("#org.angularjs.cli.AngularCliConfigLoader")
37 fun load(project: Project, context: VirtualFile): AngularCliConfig {
38 val angularCliFolder = AngularCliUtil.findAngularCliFolder(project, context) ?: return AngularCliEmptyConfig()
39 val angularCliJson = AngularCliUtil.findCliJson(angularCliFolder) ?: return AngularCliEmptyConfig()
41 return CachedValuesManager.getManager(project).getCachedValue(
42 PsiManager.getInstance(project).findFile(angularCliJson)!!, ANGULAR_CLI_CONFIG_KEY,
44 val cachedDocument = FileDocumentManager.getInstance().getCachedDocument(angularCliJson)
45 CachedValueProvider.Result.create(
46 AngularCliJsonFileConfig(
47 angularCliJson, cachedDocument?.charsSequence ?: VfsUtilCore.loadText(angularCliJson)),
48 cachedDocument ?: angularCliJson)
51 catch (e: Exception) {
54 return AngularCliEmptyConfig()
57 interface AngularCliConfig {
59 * @return root folders according to apps -> root in .angular-cli.json; usually it is a single 'src' folder.
61 fun getRootDirs(): Collection<VirtualFile>
64 * @return folders that are precessed as root folders by style preprocessor according to apps -> stylePreprocessorOptions -> includePaths in .angular-cli.json
66 fun getStylePreprocessorIncludeDirs(): Collection<VirtualFile>
68 fun getKarmaConfigFile(): VirtualFile?
70 fun getProtractorConfigFile(): VirtualFile?
76 private class AngularCliEmptyConfig : AngularCliConfig {
78 override fun getRootDirs(): Collection<VirtualFile> = emptyList()
80 override fun getStylePreprocessorIncludeDirs(): Collection<VirtualFile> = emptyList()
82 override fun getKarmaConfigFile(): VirtualFile? = null
84 override fun getProtractorConfigFile(): VirtualFile? = null
86 override fun exists(): Boolean = false
90 private class AngularCliJsonFileConfig(angularCliJson: VirtualFile, text: CharSequence) : AngularCliConfig {
92 private val myAngularCliJson: VirtualFile = angularCliJson
93 private val myRootPaths: List<String>
94 private val myStylePreprocessorIncludePaths: List<String>
95 private val myKarmaConfigPath: String?
96 private val myProtractorConfigPath: String?
99 val ngCliConfig = GsonBuilder().setLenient().create().fromJson(CharSequenceReader(text), AngularCli::class.java)
100 val allProjects = ContainerUtil.concat(ngCliConfig.apps, ngCliConfig.projects.values.toList())
101 myRootPaths = allProjects.mapNotNull { it.rootPath }.fold(ArrayList()) { acc, root -> acc.add(root); acc; }
102 myStylePreprocessorIncludePaths = ContainerUtil.concat(
103 allProjects.mapNotNull { it.stylePreprocessorOptions?.includePaths },
104 allProjects.mapNotNull { it.targets?.build?.options?.stylePreprocessorOptions?.includePaths }
105 ).fold(ArrayList()) { acc, list -> acc.addAll(list); acc; }
106 myKarmaConfigPath = allProjects.mapNotNull { it.targets?.test?.options?.karmaConfig }.firstOrNull()
107 myProtractorConfigPath = allProjects.mapNotNull { it.targets?.e2e?.options?.protractorConfig }.firstOrNull()
110 override fun getRootDirs(): Collection<VirtualFile> {
111 val angularCliFolder = myAngularCliJson.parent
112 return myRootPaths.mapNotNull { s -> angularCliFolder.findFileByRelativePath(s) }
115 override fun getStylePreprocessorIncludeDirs(): Collection<VirtualFile> {
116 val angularCliFolder = myAngularCliJson.parent
117 val result = ArrayList<VirtualFile>(myRootPaths.size * myStylePreprocessorIncludePaths.size)
118 for (rootPath in myRootPaths) {
119 for (includePath in myStylePreprocessorIncludePaths) {
120 ContainerUtil.addIfNotNull(result, angularCliFolder.findFileByRelativePath("$rootPath/$includePath"))
126 override fun getKarmaConfigFile(): VirtualFile? {
127 return myAngularCliJson.parent.findFileByRelativePath(myKarmaConfigPath ?: return null)
130 override fun getProtractorConfigFile(): VirtualFile? {
131 return myAngularCliJson.parent.findFileByRelativePath(myProtractorConfigPath ?: return null)
134 override fun exists(): Boolean = true
138 private class AngularCli {
139 @SerializedName("apps")
141 val apps: List<AngularCliProject> = ArrayList()
143 @SerializedName("projects")
145 val projects: Map<String, AngularCliProject> = HashMap()
148 private class AngularCliProject {
149 @SerializedName("root")
151 val rootPath: String? = null
153 @SerializedName("stylePreprocessorOptions")
155 val stylePreprocessorOptions: AngularCliStylePreprocessorOptions? = null
157 @SerializedName("targets", alternate = ["architect"])
159 val targets: AngularCliTargets? = null
163 private class AngularCliTargets {
164 @SerializedName("build")
166 val build: AngularCliBuild? = null
168 @SerializedName("test")
170 val test: AngularCliTest? = null
172 @SerializedName("e2e")
174 val e2e: AngularCliE2E? = null
177 private class AngularCliE2E {
178 @SerializedName("options")
180 val options: AngularCliE2EOptions? = null
183 private class AngularCliE2EOptions {
184 @SerializedName("protractorConfig")
186 val protractorConfig: String? = null
189 private class AngularCliTest {
190 @SerializedName("options")
192 val options: AngularCliTestOptions? = null
195 private class AngularCliTestOptions {
196 @SerializedName("karmaConfig")
198 val karmaConfig: String? = null
201 private class AngularCliBuild {
202 @SerializedName("options")
204 val options: AngularCliBuildOptions? = null
207 private class AngularCliBuildOptions {
208 @SerializedName("stylePreprocessorOptions")
210 val stylePreprocessorOptions: AngularCliStylePreprocessorOptions? = null
213 private class AngularCliStylePreprocessorOptions {
214 @SerializedName("includePaths")
216 val includePaths: List<String> = ArrayList()