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