001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.js.config; 018 019 import com.intellij.openapi.project.Project; 020 import com.intellij.openapi.util.Key; 021 import com.intellij.openapi.util.text.StringUtil; 022 import com.intellij.openapi.vfs.*; 023 import com.intellij.psi.PsiFile; 024 import com.intellij.psi.PsiManager; 025 import com.intellij.util.PathUtil; 026 import com.intellij.util.io.URLUtil; 027 import kotlin.Unit; 028 import kotlin.jvm.functions.Function1; 029 import kotlin.jvm.functions.Function2; 030 import org.jetbrains.annotations.NotNull; 031 import org.jetbrains.annotations.Nullable; 032 import org.jetbrains.kotlin.idea.KotlinFileType; 033 import org.jetbrains.kotlin.js.JavaScript; 034 import org.jetbrains.kotlin.psi.KtFile; 035 import org.jetbrains.kotlin.serialization.js.ModuleKind; 036 import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata; 037 import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils; 038 import org.jetbrains.kotlin.utils.LibraryUtils; 039 040 import java.io.File; 041 import java.util.Collections; 042 import java.util.List; 043 044 import static org.jetbrains.kotlin.utils.LibraryUtils.isOldKotlinJavascriptLibrary; 045 import static org.jetbrains.kotlin.utils.PathUtil.getKotlinPathsForDistDirectory; 046 047 public class LibrarySourcesConfig extends JsConfig { 048 public static final List<String> JS_STDLIB = 049 Collections.singletonList(getKotlinPathsForDistDirectory().getJsStdLibJarPath().getAbsolutePath()); 050 051 @NotNull 052 public static final Key<String> EXTERNAL_MODULE_NAME = Key.create("externalModule"); 053 @NotNull 054 public static final String UNKNOWN_EXTERNAL_MODULE_NAME = "<unknown>"; 055 056 public static final String STDLIB_JS_MODULE_NAME = "stdlib"; 057 public static final String BUILTINS_JS_MODULE_NAME = "builtins"; 058 public static final String BUILTINS_JS_FILE_NAME = BUILTINS_JS_MODULE_NAME + JavaScript.DOT_EXTENSION; 059 public static final String STDLIB_JS_FILE_NAME = STDLIB_JS_MODULE_NAME + JavaScript.DOT_EXTENSION; 060 061 private final boolean isUnitTestConfig; 062 063 @NotNull 064 private final List<String> files; 065 066 private LibrarySourcesConfig( 067 @NotNull Project project, 068 @NotNull String moduleId, 069 @NotNull ModuleKind moduleKind, 070 @NotNull List<String> files, 071 @NotNull EcmaVersion ecmaVersion, 072 boolean sourceMap, 073 boolean inlineEnabled, 074 boolean isUnitTestConfig, 075 boolean metaInfo, 076 boolean kjsm 077 ) { 078 super(project, moduleId, moduleKind, ecmaVersion, sourceMap, inlineEnabled, metaInfo, kjsm); 079 this.files = files; 080 this.isUnitTestConfig = isUnitTestConfig; 081 } 082 083 @Override 084 public boolean isTestConfig() { 085 return isUnitTestConfig; 086 } 087 088 @NotNull 089 public List<String> getLibraries() { 090 return files; 091 } 092 093 @Override 094 protected void init(@NotNull final List<KtFile> sourceFilesInLibraries, @NotNull final List<KotlinJavascriptMetadata> metadata) { 095 if (files.isEmpty()) return; 096 097 final PsiManager psiManager = PsiManager.getInstance(getProject()); 098 099 Function1<String, Unit> report = new Function1<String, Unit>() { 100 @Override 101 public Unit invoke(String message) { 102 throw new IllegalStateException(message); 103 } 104 }; 105 106 Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() { 107 @Override 108 public Unit invoke(String moduleName, VirtualFile file) { 109 if (moduleName != null) { 110 JetFileCollector jetFileCollector = new JetFileCollector(sourceFilesInLibraries, moduleName, psiManager); 111 VfsUtilCore.visitChildrenRecursively(file, jetFileCollector); 112 } 113 else { 114 String libraryPath = PathUtil.getLocalPath(file); 115 assert libraryPath != null : "libraryPath for " + file + " should not be null"; 116 metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath)); 117 } 118 119 return Unit.INSTANCE; 120 } 121 }; 122 123 boolean hasErrors = checkLibFilesAndReportErrors(report, action); 124 assert !hasErrors : "hasErrors should be false"; 125 } 126 127 @Override 128 public boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report) { 129 return checkLibFilesAndReportErrors(report, null); 130 } 131 132 private boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report, @Nullable Function2<String, VirtualFile, Unit> action) { 133 if (files.isEmpty()) { 134 return false; 135 } 136 137 VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 138 VirtualFileSystem jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL); 139 140 for (String path : files) { 141 VirtualFile file; 142 143 File filePath = new File(path); 144 if (!filePath.exists()) { 145 report.invoke("Path '" + path + "' does not exist"); 146 return true; 147 } 148 149 if (path.endsWith(".jar") || path.endsWith(".zip")) { 150 file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR); 151 } 152 else { 153 file = fileSystem.findFileByPath(path); 154 } 155 156 if (file == null) { 157 report.invoke("File '" + path + "' does not exist or could not be read"); 158 return true; 159 } 160 161 String moduleName; 162 163 if (isOldKotlinJavascriptLibrary(filePath)) { 164 moduleName = LibraryUtils.getKotlinJsModuleName(filePath); 165 } 166 else { 167 List<KotlinJavascriptMetadata> metadataList = KotlinJavascriptMetadataUtils.loadMetadata(filePath); 168 if (metadataList.isEmpty()) { 169 report.invoke("'" + path + "' is not a valid Kotlin Javascript library"); 170 return true; 171 } 172 173 for (KotlinJavascriptMetadata metadata : metadataList) { 174 if (!metadata.isAbiVersionCompatible()) { 175 report.invoke("File '" + path + "' was compiled with an incompatible version of Kotlin. " + 176 "Its ABI version is " + metadata.getAbiVersion() + 177 ", expected ABI version is " + KotlinJavascriptMetadataUtils.ABI_VERSION); 178 return true; 179 } 180 } 181 182 moduleName = null; 183 } 184 185 if (action != null) { 186 action.invoke(moduleName, file); 187 } 188 } 189 190 return false; 191 } 192 193 public static class Builder { 194 Project project; 195 String moduleId; 196 @NotNull 197 ModuleKind moduleKind = ModuleKind.PLAIN; 198 List<String> files; 199 @NotNull 200 EcmaVersion ecmaVersion = EcmaVersion.defaultVersion(); 201 boolean sourceMap = false; 202 boolean inlineEnabled = true; 203 boolean isUnitTestConfig = false; 204 boolean metaInfo = false; 205 boolean kjsm = false; 206 207 public Builder(@NotNull Project project, @NotNull String moduleId, @NotNull List<String> files) { 208 this.project = project; 209 this.moduleId = moduleId; 210 this.files = files; 211 } 212 213 public Builder ecmaVersion(@NotNull EcmaVersion ecmaVersion) { 214 this.ecmaVersion = ecmaVersion; 215 return this; 216 } 217 218 public Builder sourceMap(boolean sourceMap) { 219 this.sourceMap = sourceMap; 220 return this; 221 } 222 223 public Builder inlineEnabled(boolean inlineEnabled) { 224 this.inlineEnabled = inlineEnabled; 225 return this; 226 } 227 228 public Builder isUnitTestConfig(boolean isUnitTestConfig) { 229 this.isUnitTestConfig = isUnitTestConfig; 230 return this; 231 } 232 233 public Builder metaInfo(boolean metaInfo) { 234 this.metaInfo = metaInfo; 235 return this; 236 } 237 238 public Builder kjsm(boolean kjsm) { 239 this.kjsm = kjsm; 240 return this; 241 } 242 243 @NotNull 244 public Builder moduleKind(@NotNull ModuleKind moduleKind) { 245 this.moduleKind = moduleKind; 246 return this; 247 } 248 249 public JsConfig build() { 250 return new LibrarySourcesConfig(project, moduleId, moduleKind, files, ecmaVersion, sourceMap, inlineEnabled, isUnitTestConfig, 251 metaInfo, kjsm); 252 } 253 } 254 255 protected static KtFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) { 256 PsiFile psiFile = psiManager.findFile(file); 257 assert psiFile != null; 258 259 setupPsiFile(psiFile, moduleName); 260 return (KtFile) psiFile; 261 } 262 263 protected static void setupPsiFile(PsiFile psiFile, String moduleName) { 264 psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName); 265 } 266 267 private static class JetFileCollector extends VirtualFileVisitor { 268 private final List<KtFile> jetFiles; 269 private final String moduleName; 270 private final PsiManager psiManager; 271 272 private JetFileCollector(List<KtFile> files, String name, PsiManager manager) { 273 moduleName = name; 274 psiManager = manager; 275 jetFiles = files; 276 } 277 278 @Override 279 public boolean visitFile(@NotNull VirtualFile file) { 280 if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(KotlinFileType.EXTENSION)) { 281 jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager)); 282 } 283 return true; 284 } 285 } 286 }