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.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<KtFile> 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.isAbiVersionCompatible()) { 172 report.invoke("File '" + path + "' was compiled with an incompatible version of Kotlin. " + 173 "Its ABI version is " + metadata.getAbiVersion() + 174 ", expected ABI version is " + KotlinJavascriptMetadataUtils.ABI_VERSION); 175 return true; 176 } 177 } 178 179 moduleName = null; 180 } 181 182 if (action != null) { 183 action.invoke(moduleName, file); 184 } 185 } 186 187 return false; 188 } 189 190 public static class Builder { 191 Project project; 192 String moduleId; 193 List<String> files; 194 @NotNull 195 EcmaVersion ecmaVersion = EcmaVersion.defaultVersion(); 196 boolean sourceMap = false; 197 boolean inlineEnabled = true; 198 boolean isUnitTestConfig = false; 199 boolean metaInfo = false; 200 201 public Builder(@NotNull Project project, @NotNull String moduleId, @NotNull List<String> files) { 202 this.project = project; 203 this.moduleId = moduleId; 204 this.files = files; 205 } 206 207 public Builder ecmaVersion(@NotNull EcmaVersion ecmaVersion) { 208 this.ecmaVersion = ecmaVersion; 209 return this; 210 } 211 212 public Builder sourceMap(boolean sourceMap) { 213 this.sourceMap = sourceMap; 214 return this; 215 } 216 217 public Builder inlineEnabled(boolean inlineEnabled) { 218 this.inlineEnabled = inlineEnabled; 219 return this; 220 } 221 222 public Builder isUnitTestConfig(boolean isUnitTestConfig) { 223 this.isUnitTestConfig = isUnitTestConfig; 224 return this; 225 } 226 227 public Builder metaInfo(boolean metaInfo) { 228 this.metaInfo = metaInfo; 229 return this; 230 } 231 232 public Config build() { 233 return new LibrarySourcesConfig(project, moduleId, files, ecmaVersion, sourceMap, inlineEnabled, isUnitTestConfig, metaInfo); 234 } 235 } 236 237 protected static KtFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) { 238 PsiFile psiFile = psiManager.findFile(file); 239 assert psiFile != null; 240 241 setupPsiFile(psiFile, moduleName); 242 return (KtFile) psiFile; 243 } 244 245 protected static void setupPsiFile(PsiFile psiFile, String moduleName) { 246 psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName); 247 } 248 249 private static class JetFileCollector extends VirtualFileVisitor { 250 private final List<KtFile> jetFiles; 251 private final String moduleName; 252 private final PsiManager psiManager; 253 254 private JetFileCollector(List<KtFile> files, String name, PsiManager manager) { 255 moduleName = name; 256 psiManager = manager; 257 jetFiles = files; 258 } 259 260 @Override 261 public boolean visitFile(@NotNull VirtualFile file) { 262 if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(KotlinFileType.EXTENSION)) { 263 jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager)); 264 } 265 return true; 266 } 267 } 268 }