Skip to content

Commit

Permalink
Allow applying input source maps to the generated source map.
Browse files Browse the repository at this point in the history
This allows users to easily map from generated source that's an
input to the compiler back into their original source files.

E.g. when compiling TypeScript, users would have:
    input.ts -(a)-> input.js -(b)-> compiled.js

Where (a) is the TypeScript compilation operation, and (b) is Closure
Compiler. With this option set, the source map generated in (a) is
applied to the operation (b), with the final source map mapping back
into `input.ts` from locations in `compiled.js`.

This also introduces an interface `SourceFileMapping` to avoid
increasing the coupling and responsibility of the Compiler object.

Merge pull request #1971 from mprobst/closure-compiler
Closes #1971

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=131651810
  • Loading branch information
mprobst authored and brad4d committed Aug 30, 2016
1 parent cd3d81d commit daa73e1
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 44 deletions.
12 changes: 12 additions & 0 deletions src/com/google/javascript/jscomp/AbstractCommandLineRunner.java
Expand Up @@ -382,6 +382,7 @@ protected void setRunOptions(CompilerOptions options) throws IOException {
options.sourceMapDetailLevel = config.sourceMapDetailLevel;
options.sourceMapFormat = config.sourceMapFormat;
options.sourceMapLocationMappings = config.sourceMapLocationMappings;
options.applyInputSourceMaps = config.applyInputSourceMaps;

ImmutableMap.Builder<String, SourceMapInput> inputSourceMaps
= new ImmutableMap.Builder<>();
Expand Down Expand Up @@ -2245,6 +2246,17 @@ public CommandLineConfig setSourceMapLocationMappings(
return this;
}

private boolean applyInputSourceMaps = false;

/**
* Whether to apply input source maps to the output, i.e. map back to original inputs from
* input files that have source maps applied to them.
*/
public CommandLineConfig setApplyInputSourceMaps(boolean applyInputSourceMaps) {
this.applyInputSourceMaps = applyInputSourceMaps;
return this;
}

private ArrayList<FlagEntry<CheckLevel>> warningGuards = new ArrayList<>();

/**
Expand Down
94 changes: 52 additions & 42 deletions src/com/google/javascript/jscomp/CommandLineRunner.java
Expand Up @@ -186,11 +186,11 @@ private static class Flags {

@Option(name = "--js",
handler = JsOptionHandler.class,
usage = "The JavaScript filename. You may specify multiple. " +
"The flag name is optional, because args are interpreted as files by default. " +
"You may also use minimatch-style glob patterns. For example, use " +
"--js='**.js' --js='!**_test.js' to recursively include all " +
"js files that do not end in _test.js")
usage = "The JavaScript filename. You may specify multiple. "
+ "The flag name is optional, because args are interpreted as files by default. "
+ "You may also use minimatch-style glob patterns. For example, use "
+ "--js='**.js' --js='!**_test.js' to recursively include all "
+ "js files that do not end in _test.js")
private List<String> js = new ArrayList<>();

@Option(name = "--jszip",
Expand All @@ -200,8 +200,8 @@ private static class Flags {
private List<String> jszip = new ArrayList<>();

@Option(name = "--js_output_file",
usage = "Primary output filename. If not specified, output is " +
"written to stdout")
usage = "Primary output filename. If not specified, output is "
+ "written to stdout")
private String jsOutputFile = "";

@Option(name = "--module",
Expand Down Expand Up @@ -279,30 +279,37 @@ private static class Flags {
private String moduleOutputPathPrefix = "./";

@Option(name = "--create_source_map",
usage = "If specified, a source map file mapping the generated " +
"source files back to the original source file will be " +
"output to the specified path. The %outname% placeholder will " +
"expand to the name of the output file that the source map " +
"corresponds to.")
usage = "If specified, a source map file mapping the generated "
+ "source files back to the original source file will be "
+ "output to the specified path. The %outname% placeholder will "
+ "expand to the name of the output file that the source map "
+ "corresponds to.")
private String createSourceMap = "";

@Option(name = "--source_map_format",
hidden = true,
usage = "The source map format to produce. " +
"Options are V3 and DEFAULT, which are equivalent.")
usage = "The source map format to produce. "
+ "Options are V3 and DEFAULT, which are equivalent.")
private SourceMap.Format sourceMapFormat = SourceMap.Format.DEFAULT;

@Option(name = "--source_map_location_mapping",
usage = "Source map location mapping separated by a '|' " +
"(i.e. filesystem-path|webserver-path)")
usage = "Source map location mapping separated by a '|' "
+ "(i.e. filesystem-path|webserver-path)")
private List<String> sourceMapLocationMapping = new ArrayList<>();

@Option(name = "--source_map_input",
hidden = true,
usage = "Source map locations for input files, separated by a '|', " +
"(i.e. input-file-path|input-source-map)")
usage = "Source map locations for input files, separated by a '|', "
+ "(i.e. input-file-path|input-source-map)")
private List<String> sourceMapInputs = new ArrayList<>();

@Option(name = "--apply_input_source_maps",
handler = BooleanOptionHandler.class,
hidden = true,
usage = "Whether to apply input source maps to the output source map, "
+ "i.e. have the result map back to original inputs")
private boolean applyInputSourceMaps = false;

This comment has been minimized.

Copy link
@ChadKillingsworth

ChadKillingsworth Aug 31, 2016

Collaborator

I really think this should default to true for the open source command line runner. Did it get changed to false in the internal CL on purpose?


// Used to define the flag, values are stored by the handler.
@SuppressWarnings("unused")
@Option(
Expand Down Expand Up @@ -338,24 +345,24 @@ private static class Flags {

@Option(name = "--define",
aliases = {"--D", "-D"},
usage = "Override the value of a variable annotated @define. " +
"The format is <name>[=<val>], where <name> is the name of a @define " +
"variable and <val> is a boolean, number, or a single-quoted string " +
"that contains no single quotes. If [=<val>] is omitted, " +
"the variable is marked true")
usage = "Override the value of a variable annotated @define. "
+ "The format is <name>[=<val>], where <name> is the name of a @define "
+ "variable and <val> is a boolean, number, or a single-quoted string "
+ "that contains no single quotes. If [=<val>] is omitted, "
+ "the variable is marked true")
private List<String> define = new ArrayList<>();

@Option(name = "--charset",
usage = "Input and output charset for all files. By default, we " +
"accept UTF-8 as input and output US_ASCII")
usage = "Input and output charset for all files. By default, we "
+ "accept UTF-8 as input and output US_ASCII")
private String charset = "";

@Option(name = "--compilation_level",
aliases = {"-O"},
usage = "Specifies the compilation level to use. Options: " +
"WHITESPACE_ONLY, " +
"SIMPLE, " +
"ADVANCED")
usage = "Specifies the compilation level to use. Options: "
+ "WHITESPACE_ONLY, "
+ "SIMPLE, "
+ "ADVANCED")
private String compilationLevel = "SIMPLE";
private CompilationLevel compilationLevelParsed = null;

Expand All @@ -367,9 +374,9 @@ private static class Flags {

@Option(name = "--use_types_for_optimization",
handler = BooleanOptionHandler.class,
usage = "Enable or disable the optimizations " +
"based on available type information. Inaccurate type annotations " +
"may result in incorrect results.")
usage = "Enable or disable the optimizations "
+ "based on available type information. Inaccurate type annotations "
+ "may result in incorrect results.")
private boolean useTypesForOptimization = true;

@Option(name = "--assume_function_wrapper",
Expand All @@ -382,8 +389,8 @@ private static class Flags {

@Option(name = "--warning_level",
aliases = {"-W"},
usage = "Specifies the warning level to use. Options: " +
"QUIET, DEFAULT, VERBOSE")
usage = "Specifies the warning level to use. Options: "
+ "QUIET, DEFAULT, VERBOSE")
private WarningLevel warningLevel = WarningLevel.DEFAULT;

@Option(name = "--debug",
Expand Down Expand Up @@ -542,9 +549,9 @@ private static class Flags {

@Option(name = "--translations_project",
hidden = true,
usage = "Scopes all translations to the specified project." +
"When specified, we will use different message ids so that messages " +
"in different projects can have different translations.")
usage = "Scopes all translations to the specified project."
+ "When specified, we will use different message ids so that messages "
+ "in different projects can have different translations.")
private String translationsProject = null;

@Option(name = "--flagfile",
Expand All @@ -553,9 +560,9 @@ private static class Flags {
private String flagFile = "";

@Option(name = "--warnings_whitelist_file",
usage = "A file containing warnings to suppress. Each line should be " +
"of the form\n" +
"<file-name>:<line-number>? <warning-description>")
usage = "A file containing warnings to suppress. Each line should be "
+ "of the form\n"
+ "<file-name>:<line-number>? <warning-description>")
private String warningsWhitelistFile = "";

@Option(name = "--hide_warnings_for",
Expand All @@ -569,8 +576,8 @@ private static class Flags {

@Option(name = "--tracer_mode",
hidden = true,
usage = "Shows the duration of each compiler pass and the impact to " +
"the compiled output size. Options: ALL, RAW_SIZE, TIMING_ONLY, OFF")
usage = "Shows the duration of each compiler pass and the impact to "
+ "the compiled output size. Options: ALL, RAW_SIZE, TIMING_ONLY, OFF")
private CompilerOptions.TracerMode tracerMode =
CompilerOptions.TracerMode.OFF;

Expand Down Expand Up @@ -1273,6 +1280,7 @@ private void initConfigFromFlags(String[] args, PrintStream out, PrintStream err
List<FlagEntry<JsSourceType>> mixedSources = null;
List<LocationMapping> mappings = null;
ImmutableMap<String, String> sourceMapInputs = null;
boolean applyInputSourceMaps = false;
try {
flags.parse(processedArgs);

Expand All @@ -1285,6 +1293,7 @@ private void initConfigFromFlags(String[] args, PrintStream out, PrintStream err
mixedSources = flags.getMixedJsSources();
mappings = flags.getSourceMapLocationMappings();
sourceMapInputs = flags.getSourceMapInputs();
applyInputSourceMaps = flags.applyInputSourceMaps;
} catch (CmdLineException e) {
reportError(e.getMessage());
} catch (IOException ioErr) {
Expand Down Expand Up @@ -1419,6 +1428,7 @@ private void initConfigFromFlags(String[] args, PrintStream out, PrintStream err
.setSourceMapFormat(flags.sourceMapFormat)
.setSourceMapLocationMappings(mappings)
.setSourceMapInputFiles(sourceMapInputs)
.setApplyInputSourceMaps(applyInputSourceMaps)
.setWarningGuards(Flags.guardLevels)
.setDefine(flags.define)
.setCharset(flags.charset)
Expand Down
5 changes: 4 additions & 1 deletion src/com/google/javascript/jscomp/Compiler.java
Expand Up @@ -80,7 +80,7 @@
* window, document.
*
*/
public class Compiler extends AbstractCompiler implements ErrorHandler {
public class Compiler extends AbstractCompiler implements ErrorHandler, SourceFileMapping {
static final String SINGLETON_MODULE_NAME = "$singleton$";

static final DiagnosticType MODULE_DEPENDENCY_ERROR =
Expand Down Expand Up @@ -487,6 +487,9 @@ private void initBasedOnOptions() {
if (options.sourceMapOutputPath != null) {
sourceMap = options.sourceMapFormat.getInstance();
sourceMap.setPrefixMappings(options.sourceMapLocationMappings);
if (options.applyInputSourceMaps) {
sourceMap.setSourceFileMapping(this);
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/com/google/javascript/jscomp/CompilerOptions.java
Expand Up @@ -948,6 +948,12 @@ public void setTracerMode(TracerMode mode) {
public SourceMap.Format sourceMapFormat =
SourceMap.Format.DEFAULT;

/**
* Whether to apply input source maps to the output, i.e. map back to original inputs from
* input files that have source maps applied to them.
*/
boolean applyInputSourceMaps = false;

public List<SourceMap.LocationMapping> sourceMapLocationMappings =
Collections.emptyList();

Expand Down Expand Up @@ -2456,6 +2462,10 @@ public void setSourceMapOutputPath(String sourceMapOutputPath) {
this.sourceMapOutputPath = sourceMapOutputPath;
}

public void setApplyInputSourceMaps(boolean applyInputSourceMaps) {
this.applyInputSourceMaps = applyInputSourceMaps;
}

public void setSourceMapIncludeSourcesContent(boolean sourceMapIncludeSourcesContent) {
this.sourceMapIncludeSourcesContent = sourceMapIncludeSourcesContent;
}
Expand Down
36 changes: 36 additions & 0 deletions src/com/google/javascript/jscomp/SourceFileMapping.java
@@ -0,0 +1,36 @@
/*
* Copyright 2016 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;

import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
import javax.annotation.Nullable;

/**
* A SourceFileMapping maps a source file, line, and column into an {@link OriginalMapping}.
*
* @see com.google.debugging.sourcemap.SourceMapping
*/
public interface SourceFileMapping {
/**
* Returns the original mapping for the file name, line number and column position found
* in the source map. Returns {@code null} if none is found.
*
* @param lineNo The line number, 1-based.
* @param columnNo The column index, 1-based.
*/
@Nullable
OriginalMapping getSourceMapping(String fileName, int lineNo, int columnNo);
}
26 changes: 25 additions & 1 deletion src/com/google/javascript/jscomp/SourceMap.java
Expand Up @@ -21,6 +21,7 @@
import com.google.debugging.sourcemap.SourceMapFormat;
import com.google.debugging.sourcemap.SourceMapGenerator;
import com.google.debugging.sourcemap.SourceMapGeneratorFactory;
import com.google.debugging.sourcemap.proto.Mapping.OriginalMapping;
import com.google.javascript.rhino.Node;

import java.io.IOException;
Expand All @@ -30,6 +31,7 @@
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
* Collects information mapping the generated (compiled) source back to
Expand Down Expand Up @@ -113,6 +115,13 @@ public String toString() {
private List<LocationMapping> prefixMappings = Collections.emptyList();
private final Map<String, String> sourceLocationFixupCache =
new HashMap<>();
/**
* A mapping derived from input source maps. Maps back to input sources that inputs to this
* compilation job have been generated from, and used to create a source map that maps all the way
* back to original inputs. {@code null} if no such mapping is wanted.
*/
@Nullable
private SourceFileMapping mapping;

private SourceMap(SourceMapGenerator generator) {
this.generator = generator;
Expand All @@ -131,6 +140,17 @@ public void addMapping(
return;
}

int lineNo = node.getLineno();
int charNo = node.getCharno();
if (mapping != null) {
OriginalMapping sourceMapping = mapping.getSourceMapping(sourceFile, lineNo, charNo);
if (sourceMapping != null) {
sourceFile = sourceMapping.getOriginalFile();
lineNo = sourceMapping.getLineNumber();
charNo = sourceMapping.getColumnPosition();
}
}

sourceFile = fixupSourceLocation(sourceFile);

String originalName = node.getOriginalName();
Expand All @@ -141,7 +161,7 @@ public void addMapping(

generator.addMapping(
sourceFile, originalName,
new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()),
new FilePosition(lineNo - lineBaseOffset, charNo),
outputStartPosition, outputEndPosition);
}

Expand Down Expand Up @@ -212,4 +232,8 @@ public void validate(boolean validate) {
public void setPrefixMappings(List<LocationMapping> sourceMapLocationMappings) {
this.prefixMappings = sourceMapLocationMappings;
}

public void setSourceFileMapping(SourceFileMapping mapping) {
this.mapping = mapping;
}
}
3 changes: 3 additions & 0 deletions src/com/google/javascript/jscomp/ant/CompileTask.java
Expand Up @@ -99,6 +99,7 @@ public final class CompileTask
private String sourceMapFormat;
private File sourceMapOutputFile;
private String sourceMapLocationMapping;
private boolean applyInputSourceMaps;

public CompileTask() {
this.languageIn = CompilerOptions.LanguageMode.ECMASCRIPT6;
Expand Down Expand Up @@ -487,6 +488,8 @@ private CompilerOptions createCompilerOptions() {
options.setSourceMapLocationMappings(Arrays.asList(lm));
}

options.setApplyInputSourceMaps(applyInputSourceMaps);

if (sourceMapOutputFile != null) {
File parentFile = sourceMapOutputFile.getParentFile();
if (parentFile.mkdirs()) {
Expand Down

0 comments on commit daa73e1

Please sign in to comment.