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.JetFileType; 033 import org.jetbrains.kotlin.js.JavaScript; 034 import org.jetbrains.kotlin.psi.JetFile; 035 import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata; 036 import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils; 037 import org.jetbrains.kotlin.utils.LibraryUtils; 038 039 import java.io.File; 040 import java.util.Collections; 041 import java.util.List; 042 043 import static org.jetbrains.kotlin.utils.LibraryUtils.isOldKotlinJavascriptLibrary; 044 import static org.jetbrains.kotlin.utils.PathUtil.getKotlinPathsForDistDirectory; 045 046 public class LibrarySourcesConfig extends Config { 047 public static final List<String> JS_STDLIB = 048 Collections.singletonList(getKotlinPathsForDistDirectory().getJsStdLibJarPath().getAbsolutePath()); 049 050 @NotNull 051 public static final Key<String> EXTERNAL_MODULE_NAME = Key.create("externalModule"); 052 @NotNull 053 public static final String UNKNOWN_EXTERNAL_MODULE_NAME = "<unknown>"; 054 055 public static final String STDLIB_JS_MODULE_NAME = "stdlib"; 056 public static final String BUILTINS_JS_MODULE_NAME = "builtins"; 057 public static final String BUILTINS_JS_FILE_NAME = BUILTINS_JS_MODULE_NAME + JavaScript.DOT_EXTENSION; 058 public static final String STDLIB_JS_FILE_NAME = STDLIB_JS_MODULE_NAME + JavaScript.DOT_EXTENSION; 059 060 private final boolean isUnitTestConfig; 061 062 @NotNull 063 private final List<String> files; 064 065 private LibrarySourcesConfig( 066 @NotNull Project project, 067 @NotNull String moduleId, 068 @NotNull List<String> files, 069 @NotNull EcmaVersion ecmaVersion, 070 boolean sourceMap, 071 boolean inlineEnabled, 072 boolean isUnitTestConfig, 073 boolean metaInfo 074 ) { 075 super(project, moduleId, ecmaVersion, sourceMap, inlineEnabled, metaInfo); 076 this.files = files; 077 this.isUnitTestConfig = isUnitTestConfig; 078 } 079 080 @Override 081 public boolean isTestConfig() { 082 return isUnitTestConfig; 083 } 084 085 @NotNull 086 public List<String> getLibraries() { 087 return files; 088 } 089 090 @Override 091 protected void init(@NotNull final List<JetFile> sourceFilesInLibraries, @NotNull final List<KotlinJavascriptMetadata> metadata) { 092 if (files.isEmpty()) return; 093 094 final PsiManager psiManager = PsiManager.getInstance(getProject()); 095 096 Function1<String, Unit> report = new Function1<String, Unit>() { 097 @Override 098 public Unit invoke(String message) { 099 throw new IllegalStateException(message); 100 } 101 }; 102 103 Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() { 104 @Override 105 public Unit invoke(String moduleName, VirtualFile file) { 106 if (moduleName != null) { 107 JetFileCollector jetFileCollector = new JetFileCollector(sourceFilesInLibraries, moduleName, psiManager); 108 VfsUtilCore.visitChildrenRecursively(file, jetFileCollector); 109 } 110 else { 111 String libraryPath = PathUtil.getLocalPath(file); 112 assert libraryPath != null : "libraryPath for " + file + " should not be null"; 113 metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath)); 114 } 115 116 return Unit.INSTANCE$; 117 } 118 }; 119 120 boolean hasErrors = checkLibFilesAndReportErrors(report, action); 121 assert !hasErrors : "hasErrors should be false"; 122 } 123 124 @Override 125 public boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report) { 126 return checkLibFilesAndReportErrors(report, null); 127 } 128 129 private boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report, @Nullable Function2<String, VirtualFile, Unit> action) { 130 if (files.isEmpty()) { 131 return false; 132 } 133 134 VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 135 VirtualFileSystem jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL); 136 137 for (String path : files) { 138 VirtualFile file; 139 140 File filePath = new File(path); 141 if (!filePath.exists()) { 142 report.invoke("Path '" + path + "' does not exist"); 143 return true; 144 } 145 146 if (path.endsWith(".jar") || path.endsWith(".zip")) { 147 file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR); 148 } 149 else { 150 file = fileSystem.findFileByPath(path); 151 } 152 153 if (file == null) { 154 report.invoke("File '" + path + "' does not exist or could not be read"); 155 return true; 156 } 157 158 String moduleName; 159 160 if (isOldKotlinJavascriptLibrary(filePath)) { 161 moduleName = LibraryUtils.getKotlinJsModuleName(filePath); 162 } 163 else { 164 List<KotlinJavascriptMetadata> metadataList = KotlinJavascriptMetadataUtils.loadMetadata(filePath); 165 if (metadataList.isEmpty()) { 166 report.invoke("'" + path + "' is not a valid Kotlin Javascript library"); 167 return true; 168 } 169 170 for (KotlinJavascriptMetadata metadata : metadataList) { 171 if (!metadata.getIsAbiVersionCompatible()) { 172 report.invoke("File '" + path + "' was compiled with an incompatible version of Kotlin. Its ABI version is " + metadata.getAbiVersion() + ", expected ABI version is " + KotlinJavascriptMetadataUtils.ABI_VERSION); 173 return true; 174 } 175 } 176 177 moduleName = null; 178 } 179 180 if (action != null) { 181 action.invoke(moduleName, file); 182 } 183 } 184 185 return false; 186 } 187 188 public static class Builder { 189 Project project; 190 String moduleId; 191 List<String> files; 192 @NotNull 193 EcmaVersion ecmaVersion = EcmaVersion.defaultVersion(); 194 boolean sourceMap = false; 195 boolean inlineEnabled = true; 196 boolean isUnitTestConfig = false; 197 boolean metaInfo = false; 198 199 public Builder(@NotNull Project project, @NotNull String moduleId, @NotNull List<String> files) { 200 this.project = project; 201 this.moduleId = moduleId; 202 this.files = files; 203 } 204 205 public Builder ecmaVersion(@NotNull EcmaVersion ecmaVersion) { 206 this.ecmaVersion = ecmaVersion; 207 return this; 208 } 209 210 public Builder sourceMap(boolean sourceMap) { 211 this.sourceMap = sourceMap; 212 return this; 213 } 214 215 public Builder inlineEnabled(boolean inlineEnabled) { 216 this.inlineEnabled = inlineEnabled; 217 return this; 218 } 219 220 public Builder isUnitTestConfig(boolean isUnitTestConfig) { 221 this.isUnitTestConfig = isUnitTestConfig; 222 return this; 223 } 224 225 public Builder metaInfo(boolean metaInfo) { 226 this.metaInfo = metaInfo; 227 return this; 228 } 229 230 public Config build() { 231 return new LibrarySourcesConfig(project, moduleId, files, ecmaVersion, sourceMap, inlineEnabled, isUnitTestConfig, metaInfo); 232 } 233 } 234 235 protected static JetFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) { 236 PsiFile psiFile = psiManager.findFile(file); 237 assert psiFile != null; 238 239 setupPsiFile(psiFile, moduleName); 240 return (JetFile) psiFile; 241 } 242 243 protected static void setupPsiFile(PsiFile psiFile, String moduleName) { 244 psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName); 245 } 246 247 private static class JetFileCollector extends VirtualFileVisitor { 248 private final List<JetFile> jetFiles; 249 private final String moduleName; 250 private final PsiManager psiManager; 251 252 private JetFileCollector(List<JetFile> files, String name, PsiManager manager) { 253 moduleName = name; 254 psiManager = manager; 255 jetFiles = files; 256 } 257 258 @Override 259 public boolean visitFile(@NotNull VirtualFile file) { 260 if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(JetFileType.EXTENSION)) { 261 jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager)); 262 } 263 return true; 264 } 265 } 266 }