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.cli.js;
018    
019    import com.google.common.base.Joiner;
020    import com.intellij.openapi.Disposable;
021    import com.intellij.openapi.project.Project;
022    import com.intellij.openapi.util.io.FileUtil;
023    import com.intellij.openapi.vfs.VirtualFile;
024    import com.intellij.util.Function;
025    import com.intellij.util.SmartList;
026    import com.intellij.util.containers.ContainerUtil;
027    import com.intellij.util.containers.HashMap;
028    import org.jetbrains.annotations.NotNull;
029    import org.jetbrains.annotations.Nullable;
030    import org.jetbrains.kotlin.analyzer.AnalysisResult;
031    import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
032    import org.jetbrains.kotlin.cli.common.CLICompiler;
033    import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
034    import org.jetbrains.kotlin.cli.common.ExitCode;
035    import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments;
036    import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants;
037    import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport;
038    import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation;
039    import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
040    import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
041    import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
042    import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
043    import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
044    import org.jetbrains.kotlin.config.CommonConfigurationKeys;
045    import org.jetbrains.kotlin.config.CompilerConfiguration;
046    import org.jetbrains.kotlin.config.ContentRootsKt;
047    import org.jetbrains.kotlin.config.Services;
048    import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS;
049    import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult;
050    import org.jetbrains.kotlin.js.config.EcmaVersion;
051    import org.jetbrains.kotlin.js.config.JSConfigurationKeys;
052    import org.jetbrains.kotlin.js.config.JsConfig;
053    import org.jetbrains.kotlin.js.config.LibrarySourcesConfig;
054    import org.jetbrains.kotlin.js.facade.K2JSTranslator;
055    import org.jetbrains.kotlin.js.facade.MainCallParameters;
056    import org.jetbrains.kotlin.js.facade.TranslationResult;
057    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
058    import org.jetbrains.kotlin.psi.KtFile;
059    import org.jetbrains.kotlin.serialization.js.ModuleKind;
060    import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
061    import org.jetbrains.kotlin.utils.PathUtil;
062    
063    import java.io.File;
064    import java.util.List;
065    import java.util.Map;
066    
067    import static org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR;
068    import static org.jetbrains.kotlin.cli.common.ExitCode.OK;
069    import static org.jetbrains.kotlin.cli.common.UtilsKt.checkKotlinPackageUsage;
070    import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
071    
072    public class K2JSCompiler extends CLICompiler<K2JSCompilerArguments> {
073        private static final Map<String, ModuleKind> moduleKindMap = new HashMap<String, ModuleKind>();
074    
075        static {
076            moduleKindMap.put(K2JsArgumentConstants.MODULE_PLAIN, ModuleKind.PLAIN);
077            moduleKindMap.put(K2JsArgumentConstants.MODULE_COMMONJS, ModuleKind.COMMON_JS);
078            moduleKindMap.put(K2JsArgumentConstants.MODULE_AMD, ModuleKind.AMD);
079            moduleKindMap.put(K2JsArgumentConstants.MODULE_UMD, ModuleKind.UMD);
080        }
081    
082        public static void main(String... args) {
083            doMain(new K2JSCompiler(), args);
084        }
085    
086        @NotNull
087        @Override
088        protected K2JSCompilerArguments createArguments() {
089            return new K2JSCompilerArguments();
090        }
091    
092        @NotNull
093        @Override
094        protected ExitCode doExecute(
095                @NotNull K2JSCompilerArguments arguments, @NotNull CompilerConfiguration configuration, @NotNull Disposable rootDisposable
096        ) {
097            final MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
098    
099            if (arguments.freeArgs.isEmpty()) {
100                if (arguments.version) {
101                    return OK;
102                }
103                messageCollector.report(CompilerMessageSeverity.ERROR, "Specify at least one source file or directory", NO_LOCATION);
104                return COMPILATION_ERROR;
105            }
106    
107            ContentRootsKt.addKotlinSourceRoots(configuration, arguments.freeArgs);
108            KotlinCoreEnvironment environmentForJS =
109                    KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JS_CONFIG_FILES);
110    
111            Project project = environmentForJS.getProject();
112            List<KtFile> sourcesFiles = environmentForJS.getSourceFiles();
113    
114            environmentForJS.getConfiguration().put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.allowKotlinPackage);
115    
116            if (!checkKotlinPackageUsage(environmentForJS, sourcesFiles)) return ExitCode.COMPILATION_ERROR;
117    
118            if (arguments.outputFile == null) {
119                messageCollector.report(CompilerMessageSeverity.ERROR, "Specify output file via -output", CompilerMessageLocation.NO_LOCATION);
120                return ExitCode.COMPILATION_ERROR;
121            }
122    
123            if (messageCollector.hasErrors()) {
124                return ExitCode.COMPILATION_ERROR;
125            }
126    
127            if (sourcesFiles.isEmpty()) {
128                messageCollector.report(CompilerMessageSeverity.ERROR, "No source files", CompilerMessageLocation.NO_LOCATION);
129                return COMPILATION_ERROR;
130            }
131    
132            if (arguments.verbose) {
133                reportCompiledSourcesList(messageCollector, sourcesFiles);
134            }
135    
136            File outputFile = new File(arguments.outputFile);
137    
138            configuration.put(CommonConfigurationKeys.MODULE_NAME, FileUtil.getNameWithoutExtension(outputFile));
139    
140            JsConfig config = new LibrarySourcesConfig(project, configuration);
141            if (config.checkLibFilesAndReportErrors(new JsConfig.Reporter() {
142                @Override
143                public void error(@NotNull String message) {
144                    messageCollector.report(CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION);
145                }
146    
147                @Override
148                public void warning(@NotNull String message) {
149                    messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, message, CompilerMessageLocation.NO_LOCATION);
150                }
151            })) {
152                return COMPILATION_ERROR;
153            }
154    
155            AnalyzerWithCompilerReport analyzerWithCompilerReport = analyzeAndReportErrors(messageCollector, sourcesFiles, config);
156            if (analyzerWithCompilerReport.hasErrors()) {
157                return COMPILATION_ERROR;
158            }
159    
160            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
161    
162            AnalysisResult analysisResult = analyzerWithCompilerReport.getAnalysisResult();
163            assert analysisResult instanceof JsAnalysisResult : "analysisResult should be instance of JsAnalysisResult, but " + analysisResult;
164            JsAnalysisResult jsAnalysisResult = (JsAnalysisResult) analysisResult;
165    
166            File outputPrefixFile = null;
167            if (arguments.outputPrefix != null) {
168                outputPrefixFile = new File(arguments.outputPrefix);
169                if (!outputPrefixFile.exists()) {
170                    messageCollector.report(CompilerMessageSeverity.ERROR,
171                                            "Output prefix file '" + arguments.outputPrefix + "' not found",
172                                            CompilerMessageLocation.NO_LOCATION);
173                    return ExitCode.COMPILATION_ERROR;
174                }
175            }
176    
177            File outputPostfixFile = null;
178            if (arguments.outputPostfix != null) {
179                outputPostfixFile = new File(arguments.outputPostfix);
180                if (!outputPostfixFile.exists()) {
181                    messageCollector.report(CompilerMessageSeverity.ERROR,
182                                            "Output postfix file '" + arguments.outputPostfix + "' not found",
183                                            CompilerMessageLocation.NO_LOCATION);
184                    return ExitCode.COMPILATION_ERROR;
185                }
186            }
187    
188            MainCallParameters mainCallParameters = createMainCallParameters(arguments.main);
189            TranslationResult translationResult;
190    
191            K2JSTranslator translator = new K2JSTranslator(config);
192            try {
193                //noinspection unchecked
194                translationResult = translator.translate(sourcesFiles, mainCallParameters, jsAnalysisResult);
195            }
196            catch (Exception e) {
197                throw ExceptionUtilsKt.rethrow(e);
198            }
199    
200            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
201    
202            AnalyzerWithCompilerReport.Companion.reportDiagnostics(translationResult.getDiagnostics(), messageCollector);
203    
204            if (!(translationResult instanceof TranslationResult.Success)) return ExitCode.COMPILATION_ERROR;
205    
206            TranslationResult.Success successResult = (TranslationResult.Success) translationResult;
207            OutputFileCollection outputFiles = successResult.getOutputFiles(outputFile, outputPrefixFile, outputPostfixFile);
208    
209            if (outputFile.isDirectory()) {
210                messageCollector.report(CompilerMessageSeverity.ERROR,
211                                        "Cannot open output file '" + outputFile.getPath() + "': is a directory",
212                                        CompilerMessageLocation.NO_LOCATION);
213                return ExitCode.COMPILATION_ERROR;
214            }
215    
216            File outputDir = outputFile.getParentFile();
217            if (outputDir == null) {
218                outputDir = outputFile.getAbsoluteFile().getParentFile();
219            }
220    
221            ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
222    
223            OutputUtilsKt.writeAll(outputFiles, outputDir, messageCollector);
224    
225            return OK;
226        }
227    
228        private static void reportCompiledSourcesList(@NotNull MessageCollector messageCollector, @NotNull List<KtFile> sourceFiles) {
229            Iterable<String> fileNames = ContainerUtil.map(sourceFiles, new Function<KtFile, String>() {
230                @Override
231                public String fun(@Nullable KtFile file) {
232                    assert file != null;
233                    VirtualFile virtualFile = file.getVirtualFile();
234                    if (virtualFile != null) {
235                        return FileUtil.toSystemDependentName(virtualFile.getPath());
236                    }
237                    return file.getName() + "(no virtual file)";
238                }
239            });
240            messageCollector.report(CompilerMessageSeverity.LOGGING, "Compiling source files: " + Joiner.on(", ").join(fileNames),
241                                    CompilerMessageLocation.NO_LOCATION);
242        }
243    
244        private static AnalyzerWithCompilerReport analyzeAndReportErrors(
245                @NotNull MessageCollector messageCollector, @NotNull final List<KtFile> sources, @NotNull final JsConfig config
246        ) {
247            AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(messageCollector);
248            analyzerWithCompilerReport.analyzeAndReport(sources, new AnalyzerWithCompilerReport.Analyzer() {
249                @NotNull
250                @Override
251                public AnalysisResult analyze() {
252                    return TopDownAnalyzerFacadeForJS.analyzeFiles(sources, config);
253                }
254    
255                @Override
256                public void reportEnvironmentErrors() {
257                }
258            });
259            return analyzerWithCompilerReport;
260        }
261    
262        @Override
263        protected void setupPlatformSpecificArgumentsAndServices(
264                @NotNull CompilerConfiguration configuration, @NotNull K2JSCompilerArguments arguments,
265                @NotNull Services services
266        ) {
267            MessageCollector messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
268    
269            if (arguments.target != null) {
270                assert arguments.target == "v5" : "Unsupported ECMA version: " + arguments.target;
271            }
272            configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.defaultVersion());
273    
274            if (arguments.sourceMap) {
275                configuration.put(JSConfigurationKeys.SOURCE_MAP, true);
276            }
277            if (arguments.metaInfo) {
278                configuration.put(JSConfigurationKeys.META_INFO, true);
279            }
280    
281            List<String> libraries = new SmartList<String>();
282            if (!arguments.noStdlib) {
283                libraries.add(0, PathUtil.getKotlinPathsForCompiler().getJsStdLibJarPath().getAbsolutePath());
284            }
285    
286            if (arguments.libraries != null) {
287                ContainerUtil.addAllNotNull(libraries, arguments.libraries.split(File.pathSeparator));
288            }
289    
290            configuration.put(JSConfigurationKeys.LIBRARIES, libraries);
291    
292            String moduleKindName = arguments.moduleKind;
293            ModuleKind moduleKind = moduleKindName != null ? moduleKindMap.get(moduleKindName) : ModuleKind.PLAIN;
294            if (moduleKind == null) {
295                messageCollector.report(CompilerMessageSeverity.ERROR, "Unknown module kind: " + moduleKindName + ". " +
296                                                                       "Valid values are: plain, amd, commonjs, umd",
297                                        CompilerMessageLocation.NO_LOCATION);
298            }
299            configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind);
300        }
301    
302        private static MainCallParameters createMainCallParameters(String main) {
303            if (K2JsArgumentConstants.NO_CALL.equals(main)) {
304                return MainCallParameters.noCall();
305            }
306            else {
307                return MainCallParameters.mainWithoutArguments();
308            }
309        }
310    }