Better fix for sourceRoot and relative path for .coffee files in source maps.

This commit is contained in:
Jason Walton
2013-03-06 15:45:47 -05:00
parent d6e1a979e4
commit ce6772f2be
7 changed files with 225 additions and 48 deletions

View File

@@ -40,35 +40,28 @@
exports.helpers = helpers;
generateV3SourceMapOptions = function(options) {
var pathDepth, sourceRoot, _j;
var cwd, sourceRoot;
if (options == null) {
options = {};
}
console.log("Generating v3 source map");
cwd = options.workingDirectory;
if (!options.filename) {
return {};
} else {
if (options.jsPath) {
pathDepth = options.jsPath.split('/').length - 1;
sourceRoot = "";
if (pathDepth > 0) {
for (_j = 0; 0 <= pathDepth ? _j < pathDepth : _j > pathDepth; 0 <= pathDepth ? _j++ : _j--) {
sourceRoot += "../";
}
}
return {
sourceRoot: sourceRoot,
sourceFile: options.filename,
generatedFile: helpers.baseFileName(options.jsPath)
};
} else {
return {
sourceRoot: "",
sourceFile: helpers.baseFileName(options.filename, {
generatedFile: helpers.baseFileName(options.filename, true) + ".js"
})
};
}
}
if (options.jsPath) {
sourceRoot = helpers.relativePath(options.jsPath, ".", cwd);
return {
sourceRoot: sourceRoot,
sourceFile: helpers.relativePath(".", options.filename, cwd),
generatedFile: helpers.baseFileName(options.jsPath)
};
}
return {
sourceRoot: "",
sourceFile: helpers.baseFileName(options.filename),
generatedFile: helpers.baseFileName(options.filename, true) + ".js"
};
};
exports.compile = compile = function(code, options) {

View File

@@ -489,7 +489,8 @@
bare: opts.bare,
header: opts.compile,
sourceMap: opts.map,
jsPath: filename !== null && base !== null ? outputPath(filename, base) : null
jsPath: filename !== null && base !== null ? outputPath(filename, base) : null,
workingDirectory: process.cwd()
};
};

View File

@@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.1
(function() {
var buildLocationData, extend, flatten, _ref;
var buildLocationData, extend, flatten, normalizePath, _ref;
exports.starts = function(string, literal, start) {
return literal === string.substr(start, literal.length);
@@ -166,4 +166,74 @@
return /\.(litcoffee|coffee\.md)$/.test(file);
};
exports.normalizePath = normalizePath = function(path, removeTrailingSlash) {
var i, parts, root, _ref1;
if (removeTrailingSlash == null) {
removeTrailingSlash = false;
}
root = false;
parts = path.split('/');
i = 0;
if (parts.length > 1 && parts[i] === '') {
parts.splice(i, 1);
root = true;
}
while (i < parts.length) {
if ((_ref1 = parts[i]) === '.' || _ref1 === '') {
if ((i === parts.length - 1) && !removeTrailingSlash) {
parts[i] = '';
i++;
} else {
parts.splice(i, 1);
}
} else if (parts[i] === '..') {
if (i === 0 || (i && parts[i - 1] === '..')) {
i++;
} else {
parts.splice(i - 1, 2);
i--;
}
} else {
i++;
}
}
if (root) {
if (parts.length === 0) {
return '/';
}
if (parts.length[0] === '..') {
throw new Error("Invalid path: " + path);
}
parts.unshift('');
}
return parts.join('/');
};
exports.relativePath = function(from, to, cwd) {
var answer, _i, _ref1;
if (cwd == null) {
cwd = null;
}
if (cwd) {
from = cwd + "/" + from;
to = cwd + "/" + to;
}
from = (normalizePath(from)).split('/');
to = (normalizePath(to)).split('/');
while (from.length > 0 && to.length > 0 && from[0] === to[0]) {
from.shift();
to.shift();
}
if (from.length && from[0] === "..") {
throw new Error("'cwd' must be specified if 'from' references parent directory: " + (from.join('/')) + " -> " + (to.join('/')));
}
answer = "";
if (from.length > 1) {
for (_i = 0, _ref1 = from.length - 1; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; 0 <= _ref1 ? _i++ : _i--) {
answer += "../";
}
}
return answer + ("" + (to.join('/')));
};
}).call(this);

View File

@@ -30,33 +30,41 @@ exports.VERSION = '1.6.1'
# Expose helpers for testing.
exports.helpers = helpers
# Generate v3 Source Map options from compile options.
#
# options.filename is required, and is the path and filename of the file being compiled,
# relative to the current working directory.
#
# `options.jsPath` and `options.workingDirectory` may also be specified to customize the output
# in the resulting v3 source map, where `options.jsPath` is the path where the .js file will be
# written relative to the current working directory, and `options.workingDirectory` is the absolute
# path of the current working directory (required if jsPath references a parent directory.) If
# these options are provided, then "sourceRoot" in the output will be a relative path to the
# current working directory, and source files will be given relative to the "sourceRoot".
#
generateV3SourceMapOptions = (options = {}) ->
if !options.filename
return {}
else
if options.jsPath
# jsPath is relative to the CWD. Construct a sourceRoot that gets us back to the CWD.
pathDepth = options.jsPath.split('/').length-1
sourceRoot = ""
if (pathDepth > 0) then for [0...pathDepth]
sourceRoot += "../"
return {
sourceRoot,
sourceFile: options.filename,
generatedFile: helpers.baseFileName(options.jsPath)
}
else
return {
sourceRoot: "",
sourceFile: helpers.baseFileName options.filename,
generatedFile: helpers.baseFileName(options.filename, yes) + ".js"
}
console.log "Generating v3 source map"
cwd = options.workingDirectory
return {} unless options.filename
if options.jsPath
sourceRoot = helpers.relativePath options.jsPath, ".", cwd
return {
sourceRoot
sourceFile: helpers.relativePath ".", options.filename, cwd
generatedFile: helpers.baseFileName(options.jsPath)
}
{
sourceRoot: ""
sourceFile: helpers.baseFileName options.filename
generatedFile: helpers.baseFileName(options.filename, yes) + ".js"
}
# Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler.
#
# If `options.sourceMap` is specified, then `options.filename` must also be specified.
# If `options.sourceMap` is specified, then `options.filename` must also be specified. See
# `generateV3SourceMapOptions()` for other options that can be passed to control source map
# generation.
#
# This returns a javascript string, unless `options.sourceMap` is passed,
# in which case this returns a `{js, v3SourceMap, sourceMap}

View File

@@ -330,6 +330,7 @@ compileOptions = (filename, base) ->
header: opts.compile
sourceMap: opts.map
jsPath: if (filename isnt null and base isnt null) then (outputPath filename, base) else null
workingDirectory: process.cwd()
}

View File

@@ -120,3 +120,62 @@ exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
# Remove any "." components in a path, any ".."s in the middle of a path. Leaves a trailing '/'
# if present, unless removeTrailingSlash is set.
exports.normalizePath = normalizePath = (path, removeTrailingSlash=no) ->
root = no # Does this path start with the root?
parts = path.split '/'
i = 0
# If the path started with a '/', set the root flag.
if parts.length > 1 and parts[i] == ''
parts.splice i, 1
root = yes
while i < parts.length
if parts[i] in ['.', '']
if (i is parts.length - 1) and not removeTrailingSlash
# Leave the trailing '/''
parts[i] = ''
i++
else
# Remove the empty element
parts.splice i, 1
else if parts[i] is '..'
if i is 0 or (i and parts[i-1] is '..')
# Leave the ".."
i++
else
# Remove the '..' and the previous element
parts.splice i-1, 2
i--
else
i++
if root
if parts.length == 0 then return '/'
if parts.length[0] is '..'
# Uhh... This doesn't make any sense.
throw new Error "Invalid path: #{path}"
parts.unshift '' # Add back the leading "/"
parts.join '/'
# Solve the relative path from `from` to `to`.
#
# This is the same as node's `path.relative()`, but can be used even if we're not running in node.
# If paths are relative (don't have a leading '/') then we assume they are both relative to to
# same working directory.
#
# If `from` is a relative path that starts with '..', then `cwd` must be provided to resolve
# parent path names.
exports.relativePath = (from, to, cwd=null) ->
if cwd
from = cwd + "/" + from
to = cwd + "/" + to
from = (normalizePath from).split '/'
to = (normalizePath to).split '/'
while from.length > 0 and to.length > 0 and from[0] == to[0]
from.shift()
to.shift()
if from.length and from[0] is ".." then throw new Error "'cwd' must be specified if 'from' references parent directory: #{from.join '/'} -> #{to.join '/'}"
answer = ""
if from.length > 1 then for [0...(from.length - 1)]
answer += "../"
answer + "#{to.join '/'}"

View File

@@ -2,7 +2,7 @@
# -------
# pull the helpers from `CoffeeScript.helpers` into local variables
{starts, ends, compact, count, merge, extend, flatten, del, last} = CoffeeScript.helpers
{starts, ends, compact, count, merge, extend, flatten, del, last, normalizePath, relativePath} = CoffeeScript.helpers
# `starts`
@@ -94,3 +94,48 @@ test "the `last` helper returns the last item of an array-like object", ->
test "the `last` helper allows one to specify an optional offset", ->
ary = [0, 1, 2, 3, 4]
eq 2, last(ary, 2)
# `normalizePath`
test "various tests for normalizePath", ->
eq "/", normalizePath "/"
eq "", normalizePath "."
eq "", normalizePath ""
eq "/a/b", normalizePath "/a/b"
eq "/a/c/", normalizePath "/a/c/"
eq "/a/c", normalizePath "/a/c/", true
eq "/a/d", normalizePath "/a/../a/./d/c/.."
eq "/a/e/", normalizePath "/a/../a/./e/c/../"
eq "/a/e", normalizePath "/a/../a/./e/c/../", true
eq "../a", normalizePath "../a"
eq "../b", normalizePath "a/../../b"
# `relativePath`
test "various tests for relativePath", ->
# Same level
eq "foo.js", relativePath "foo.coffee", "foo.js"
eq "foo.js", relativePath "foo.coffee", "foo.js", "/work/src"
# Same level, but both down one level
eq "bar.js", relativePath "src/bar.coffee", "src/bar.js"
eq "bar.js", relativePath "src/bar.coffee", "src/bar.js", "/work/src"
# Sam level, using '.'' as from
eq "baz.js", relativePath ".", "baz.js"
eq "baz.js", relativePath ".", "baz.js", "/work/src"
eq "o/qux.js", relativePath ".", "o/qux.js"
eq "o/qux.js", relativePath ".", "o/qux.js", "/work/src"
# Up one level
eq "../", relativePath "src/bar.js", "."
eq "../", relativePath "src/bar.js", ".", "/work/src"
# Up and over one directory
eq "../dest/foo.js", relativePath "src/foo.coffee", "dest/foo.js"
eq "../dest/foo.js", relativePath "src/foo.coffee", "dest/foo.js", "/work/src"
# Absolute paths
eq "dest1/dest2/bar.js", relativePath "/bar.coffee", "/dest1/dest2/bar.js"
# File vs. directory - keep trailing '/'
eq "../c", relativePath "a/b/", "a/c"
eq "../d/", relativePath "a/b/", "a/d/"
# This should throw, since relativePath can't know the name of the directory that foo.coffee is in.
throws -> relativePath "../o/foo.js", "foo.coffee"
# With the CWD, this should pass.
eq "../src/foo.coffee", relativePath "../o/foo.js", "foo.coffee", "/work/src"