WEB-34558 - basic support for @angular/cli 6.2.0
[idea/contrib.git] / AngularJS / src / org / angularjs / cli / AngularCliConfigLoader.kt
1 // Copyright 2000-2018 JetBrains s.r.o.
2 //
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
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
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")
15
16 package org.angularjs.cli
17
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
33
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")
36
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()
40   try {
41     return CachedValuesManager.getManager(project).getCachedValue(
42       PsiManager.getInstance(project).findFile(angularCliJson)!!, ANGULAR_CLI_CONFIG_KEY,
43       {
44         val cachedDocument = FileDocumentManager.getInstance().getCachedDocument(angularCliJson)
45         CachedValueProvider.Result.create(
46           AngularCliJsonFileConfig(
47             angularCliJson, cachedDocument?.charsSequence ?: VfsUtilCore.loadText(angularCliJson)),
48           cachedDocument ?: angularCliJson)
49       }, false)
50   }
51   catch (e: Exception) {
52     LOG.info(e)
53   }
54   return AngularCliEmptyConfig()
55 }
56
57 interface AngularCliConfig {
58   /**
59    * @return root folders according to apps -> root in .angular-cli.json; usually it is a single 'src' folder.
60    */
61   fun getRootDirs(): Collection<VirtualFile>
62
63   /**
64    * @return folders that are precessed as root folders by style preprocessor according to apps -> stylePreprocessorOptions -> includePaths in .angular-cli.json
65    */
66   fun getStylePreprocessorIncludeDirs(): Collection<VirtualFile>
67
68   fun getKarmaConfigFile(): VirtualFile?
69
70   fun getProtractorConfigFile(): VirtualFile?
71
72   fun exists(): Boolean
73
74 }
75
76 private class AngularCliEmptyConfig : AngularCliConfig {
77
78   override fun getRootDirs(): Collection<VirtualFile> = emptyList()
79
80   override fun getStylePreprocessorIncludeDirs(): Collection<VirtualFile> = emptyList()
81
82   override fun getKarmaConfigFile(): VirtualFile? = null
83
84   override fun getProtractorConfigFile(): VirtualFile? = null
85
86   override fun exists(): Boolean = false
87
88 }
89
90 private class AngularCliJsonFileConfig(angularCliJson: VirtualFile, text: CharSequence) : AngularCliConfig {
91
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?
97
98   init {
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()
108   }
109
110   override fun getRootDirs(): Collection<VirtualFile> {
111     val angularCliFolder = myAngularCliJson.parent
112     return myRootPaths.mapNotNull { s -> angularCliFolder.findFileByRelativePath(s) }
113   }
114
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"))
121       }
122     }
123     return result
124   }
125
126   override fun getKarmaConfigFile(): VirtualFile? {
127     return myAngularCliJson.parent.findFileByRelativePath(myKarmaConfigPath ?: return null)
128   }
129
130   override fun getProtractorConfigFile(): VirtualFile? {
131     return myAngularCliJson.parent.findFileByRelativePath(myProtractorConfigPath ?: return null)
132   }
133
134   override fun exists(): Boolean = true
135
136 }
137
138 private class AngularCli {
139   @SerializedName("apps")
140   @Expose
141   val apps: List<AngularCliProject> = ArrayList()
142
143   @SerializedName("projects")
144   @Expose
145   val projects: Map<String, AngularCliProject> = HashMap()
146 }
147
148 private class AngularCliProject {
149   @SerializedName("root")
150   @Expose
151   val rootPath: String? = null
152
153   @SerializedName("stylePreprocessorOptions")
154   @Expose
155   val stylePreprocessorOptions: AngularCliStylePreprocessorOptions? = null
156
157   @SerializedName("targets", alternate = ["architect"])
158   @Expose
159   val targets: AngularCliTargets? = null
160
161 }
162
163 private class AngularCliTargets {
164   @SerializedName("build")
165   @Expose
166   val build: AngularCliBuild? = null
167
168   @SerializedName("test")
169   @Expose
170   val test: AngularCliTest? = null
171
172   @SerializedName("e2e")
173   @Expose
174   val e2e: AngularCliE2E? = null
175 }
176
177 private class AngularCliE2E {
178   @SerializedName("options")
179   @Expose
180   val options: AngularCliE2EOptions? = null
181 }
182
183 private class AngularCliE2EOptions {
184   @SerializedName("protractorConfig")
185   @Expose
186   val protractorConfig: String? = null
187 }
188
189 private class AngularCliTest {
190   @SerializedName("options")
191   @Expose
192   val options: AngularCliTestOptions? = null
193 }
194
195 private class AngularCliTestOptions {
196   @SerializedName("karmaConfig")
197   @Expose
198   val karmaConfig: String? = null
199 }
200
201 private class AngularCliBuild {
202   @SerializedName("options")
203   @Expose
204   val options: AngularCliBuildOptions? = null
205 }
206
207 private class AngularCliBuildOptions {
208   @SerializedName("stylePreprocessorOptions")
209   @Expose
210   val stylePreprocessorOptions: AngularCliStylePreprocessorOptions? = null
211 }
212
213 private class AngularCliStylePreprocessorOptions {
214   @SerializedName("includePaths")
215   @Expose
216   val includePaths: List<String> = ArrayList()
217 }
218