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