@@ -7,6 +7,10 @@ var optimist = require('optimist')
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'map': {
|
||||
'type': 'string',
|
||||
'description': 'Source Map File'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import {isArray} from "../utils";
|
||||
|
||||
try {
|
||||
var SourceMap = require('source-map'),
|
||||
SourceNode = SourceMap.SourceNode;
|
||||
} catch (err) {
|
||||
/* istanbul ignore next: tested but not covered in istanbul due to dist build */
|
||||
SourceNode = function(line, column, srcFile, chunks) {
|
||||
this.src = '';
|
||||
if (chunks) {
|
||||
this.add(chunks);
|
||||
}
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
SourceNode.prototype = {
|
||||
add: function(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src += chunks;
|
||||
},
|
||||
prepend: function(chunks) {
|
||||
if (isArray(chunks)) {
|
||||
chunks = chunks.join('');
|
||||
}
|
||||
this.src = chunks + this.src;
|
||||
},
|
||||
toStringWithSourceMap: function() {
|
||||
return {code: this.toString()};
|
||||
},
|
||||
toString: function() {
|
||||
return this.src;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function castChunk(chunk, codeGen, loc) {
|
||||
if (isArray(chunk)) {
|
||||
var ret = [];
|
||||
|
||||
for (var i = 0, len = chunk.length; i < len; i++) {
|
||||
ret.push(codeGen.wrap(chunk[i], loc));
|
||||
}
|
||||
return ret;
|
||||
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
|
||||
// Handle primitives that the SourceNode will throw up on
|
||||
return chunk+'';
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
||||
function CodeGen(srcFile) {
|
||||
this.srcFile = srcFile;
|
||||
this.source = [];
|
||||
}
|
||||
|
||||
CodeGen.prototype = {
|
||||
prepend: function(source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
push: function(source, loc) {
|
||||
this.source.push(this.wrap(source, loc));
|
||||
},
|
||||
|
||||
merge: function() {
|
||||
var source = this.empty();
|
||||
this.each(function(line) {
|
||||
source.add([' ', line, '\n']);
|
||||
});
|
||||
return source;
|
||||
},
|
||||
|
||||
each: function(iter) {
|
||||
for (var i = 0, len = this.source.length; i < len; i++) {
|
||||
iter(this.source[i]);
|
||||
}
|
||||
},
|
||||
|
||||
empty: function(loc) {
|
||||
loc = loc || this.currentLocation || {};
|
||||
return new SourceNode(loc.firstLine, loc.firstColumn, this.srcFile);
|
||||
},
|
||||
wrap: function(chunk, loc) {
|
||||
if (chunk instanceof SourceNode) {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
loc = loc || this.currentLocation || {};
|
||||
chunk = castChunk(chunk, this, loc);
|
||||
|
||||
return new SourceNode(loc.firstLine, loc.firstColumn, this.srcFile, chunk);
|
||||
},
|
||||
|
||||
functionCall: function(fn, type, params) {
|
||||
params = this.generateList(params);
|
||||
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
var pairs = [];
|
||||
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
pairs.push([this.quotedString(key), ':', castChunk(obj[key], this)]);
|
||||
}
|
||||
}
|
||||
|
||||
var ret = this.generateList(pairs);
|
||||
ret.prepend('{');
|
||||
ret.add('}');
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
||||
generateList: function(entries, loc) {
|
||||
var ret = this.empty(loc);
|
||||
|
||||
for (var i = 0, len = entries.length; i < len; i++) {
|
||||
if (i) {
|
||||
ret.add(',');
|
||||
}
|
||||
|
||||
ret.add(castChunk(entries[i], this, loc));
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateArray: function(entries, loc) {
|
||||
var ret = this.generateList(entries, loc);
|
||||
ret.prepend('[');
|
||||
ret.add(']');
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
export default CodeGen;
|
||||
|
||||
@@ -130,36 +130,36 @@ Compiler.prototype = {
|
||||
|
||||
// now that the simple mustache is resolved, we need to
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('blockValue', sexpr.id.original);
|
||||
this.opcode('pushProgram', block, program);
|
||||
this.opcode('pushProgram', block, inverse);
|
||||
this.opcode('emptyHash', block);
|
||||
this.opcode('blockValue', block, sexpr.id.original);
|
||||
} else {
|
||||
this.ambiguousSexpr(sexpr, program, inverse);
|
||||
|
||||
// now that the simple mustache is resolved, we need to
|
||||
// evaluate it by executing `blockHelperMissing`
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('ambiguousBlockValue');
|
||||
this.opcode('pushProgram', block, program);
|
||||
this.opcode('pushProgram', block, inverse);
|
||||
this.opcode('emptyHash', block);
|
||||
this.opcode('ambiguousBlockValue', block);
|
||||
}
|
||||
|
||||
this.opcode('append');
|
||||
this.opcode('append', block);
|
||||
},
|
||||
|
||||
hash: function(hash) {
|
||||
var pairs = hash.pairs, i, l;
|
||||
|
||||
this.opcode('pushHash');
|
||||
this.opcode('pushHash', hash);
|
||||
|
||||
for(i=0, l=pairs.length; i<l; i++) {
|
||||
this.pushParam(pairs[i][1]);
|
||||
}
|
||||
while(i--) {
|
||||
this.opcode('assignToHash', pairs[i][0]);
|
||||
this.opcode('assignToHash', hash, pairs[i][0]);
|
||||
}
|
||||
this.opcode('popHash');
|
||||
this.opcode('popHash', hash);
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
@@ -169,28 +169,28 @@ Compiler.prototype = {
|
||||
if (partial.hash) {
|
||||
this.accept(partial.hash);
|
||||
} else {
|
||||
this.opcode('push', 'undefined');
|
||||
this.opcode('pushLiteral', partial, 'undefined');
|
||||
}
|
||||
|
||||
if (partial.context) {
|
||||
this.accept(partial.context);
|
||||
} else {
|
||||
this.opcode('getContext', 0);
|
||||
this.opcode('pushContext');
|
||||
this.opcode('getContext', partial, 0);
|
||||
this.opcode('pushContext', partial);
|
||||
}
|
||||
|
||||
var indent = partial.indent || '';
|
||||
if (this.options.preventIndent && indent) {
|
||||
this.opcode('appendContent', indent);
|
||||
this.opcode('appendContent', partial, indent);
|
||||
indent = '';
|
||||
}
|
||||
this.opcode('invokePartial', partialName.name, indent);
|
||||
this.opcode('append');
|
||||
this.opcode('invokePartial', partial, partialName.name, indent);
|
||||
this.opcode('append', partial);
|
||||
},
|
||||
|
||||
content: function(content) {
|
||||
if (content.string) {
|
||||
this.opcode('appendContent', content.string);
|
||||
this.opcode('appendContent', content, content.string);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -198,9 +198,9 @@ Compiler.prototype = {
|
||||
this.sexpr(mustache.sexpr);
|
||||
|
||||
if(mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
this.opcode('appendEscaped', mustache);
|
||||
} else {
|
||||
this.opcode('append');
|
||||
this.opcode('append', mustache);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -209,14 +209,14 @@ Compiler.prototype = {
|
||||
name = id.parts[0],
|
||||
isBlock = program != null || inverse != null;
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
this.opcode('getContext', sexpr, id.depth);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('pushProgram', sexpr, program);
|
||||
this.opcode('pushProgram', sexpr, inverse);
|
||||
|
||||
this.ID(id);
|
||||
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
this.opcode('invokeAmbiguous', sexpr, name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
@@ -229,11 +229,11 @@ Compiler.prototype = {
|
||||
} else {
|
||||
// Simplified ID for `this`
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
this.opcode('pushContext');
|
||||
this.opcode('getContext', sexpr, id.depth);
|
||||
this.opcode('pushContext', sexpr);
|
||||
}
|
||||
|
||||
this.opcode('resolvePossibleLambda');
|
||||
this.opcode('resolvePossibleLambda', sexpr);
|
||||
},
|
||||
|
||||
helperSexpr: function(sexpr, program, inverse) {
|
||||
@@ -242,14 +242,14 @@ Compiler.prototype = {
|
||||
name = id.parts[0];
|
||||
|
||||
if (this.options.knownHelpers[name]) {
|
||||
this.opcode('invokeKnownHelper', params.length, name);
|
||||
this.opcode('invokeKnownHelper', sexpr, params.length, name);
|
||||
} else if (this.options.knownHelpersOnly) {
|
||||
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
||||
} else {
|
||||
id.falsy = true;
|
||||
|
||||
this.ID(id);
|
||||
this.opcode('invokeHelper', params.length, id.original, id.isSimple);
|
||||
this.opcode('invokeHelper', sexpr, params.length, id.original, id.isSimple);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -267,39 +267,43 @@ Compiler.prototype = {
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
this.opcode('getContext', id.depth);
|
||||
this.opcode('getContext', id, id.depth);
|
||||
|
||||
var name = id.parts[0];
|
||||
if (!name) {
|
||||
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
||||
this.opcode('pushContext');
|
||||
this.opcode('pushContext', id);
|
||||
} else {
|
||||
this.opcode('lookupOnContext', id.parts, id.falsy, id.isScoped);
|
||||
this.opcode('lookupOnContext', id, id.parts, id.falsy, id.isScoped);
|
||||
}
|
||||
},
|
||||
|
||||
DATA: function(data) {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', data.id.depth, data.id.parts);
|
||||
this.opcode('lookupData', data, data.id.depth, data.id.parts);
|
||||
},
|
||||
|
||||
STRING: function(string) {
|
||||
this.opcode('pushString', string.string);
|
||||
this.opcode('pushString', string, string.string);
|
||||
},
|
||||
|
||||
NUMBER: function(number) {
|
||||
this.opcode('pushLiteral', number.number);
|
||||
this.opcode('pushLiteral', number, number.number);
|
||||
},
|
||||
|
||||
BOOLEAN: function(bool) {
|
||||
this.opcode('pushLiteral', bool.bool);
|
||||
this.opcode('pushLiteral', bool, bool.bool);
|
||||
},
|
||||
|
||||
comment: function() {},
|
||||
|
||||
// HELPERS
|
||||
opcode: function(name) {
|
||||
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1) });
|
||||
opcode: function(name, node) {
|
||||
var loc = {
|
||||
firstLine: node.firstLine, firstColumn: node.firstColumn,
|
||||
lastLine: node.lastLine, lastColumn: node.lastColumn
|
||||
};
|
||||
this.opcodes.push({ opcode: name, args: slice.call(arguments, 2), loc: loc });
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
@@ -344,8 +348,8 @@ Compiler.prototype = {
|
||||
if(val.depth) {
|
||||
this.addDepth(val.depth);
|
||||
}
|
||||
this.opcode('getContext', val.depth || 0);
|
||||
this.opcode('pushStringParam', val.stringModeValue, val.type);
|
||||
this.opcode('getContext', val, val.depth || 0);
|
||||
this.opcode('pushStringParam', val, val.stringModeValue, val.type);
|
||||
|
||||
if (val.type === 'sexpr') {
|
||||
// Subexpressions get evaluated and passed in
|
||||
@@ -354,7 +358,7 @@ Compiler.prototype = {
|
||||
}
|
||||
} else {
|
||||
if (this.trackIds) {
|
||||
this.opcode('pushId', val.type, val.idName || val.stringModeValue);
|
||||
this.opcode('pushId', val, val.type, val.idName || val.stringModeValue);
|
||||
}
|
||||
this.accept(val);
|
||||
}
|
||||
@@ -364,13 +368,13 @@ Compiler.prototype = {
|
||||
var params = sexpr.params;
|
||||
this.pushParams(params);
|
||||
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
this.opcode('pushProgram', sexpr, program);
|
||||
this.opcode('pushProgram', sexpr, inverse);
|
||||
|
||||
if (sexpr.hash) {
|
||||
this.hash(sexpr.hash);
|
||||
} else {
|
||||
this.opcode('emptyHash');
|
||||
this.opcode('emptyHash', sexpr);
|
||||
}
|
||||
|
||||
return params;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from "../base";
|
||||
import Exception from "../exception";
|
||||
import {isArray} from "../utils";
|
||||
import CodeGen from "./code-gen";
|
||||
|
||||
function Literal(value) {
|
||||
this.value = value;
|
||||
@@ -12,15 +14,15 @@ JavaScriptCompiler.prototype = {
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name /* , type*/) {
|
||||
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
return parent + "." + name;
|
||||
return [parent, ".", name];
|
||||
} else {
|
||||
return parent + "['" + name + "']";
|
||||
return [parent, "['", name, "']"];
|
||||
}
|
||||
},
|
||||
depthedLookup: function(name) {
|
||||
this.aliases.lookup = 'this.lookup';
|
||||
|
||||
return 'lookup(depths, "' + name + '")';
|
||||
return ['lookup(depths, "', name, '")'];
|
||||
},
|
||||
|
||||
compilerInfo: function() {
|
||||
@@ -29,15 +31,23 @@ JavaScriptCompiler.prototype = {
|
||||
return [revision, versions];
|
||||
},
|
||||
|
||||
appendToBuffer: function(string) {
|
||||
appendToBuffer: function(string, location, explicit) {
|
||||
// Force a string as this simplifies the merge logic.
|
||||
if (!isArray(string)) {
|
||||
string = [string];
|
||||
}
|
||||
string = this.source.wrap(string, location);
|
||||
|
||||
if (this.environment.isSimple) {
|
||||
return "return " + string + ";";
|
||||
return ['return ', string, ';'];
|
||||
} else if (explicit) {
|
||||
// This is a case where the buffer operation occurs as a child of another
|
||||
// construct, generally braces. We have to explicitly output these buffer
|
||||
// operations to ensure that the emitted code goes in the correct location.
|
||||
return ['buffer += ', string, ';'];
|
||||
} else {
|
||||
return {
|
||||
appendToBuffer: true,
|
||||
content: string,
|
||||
toString: function() { return "buffer += " + string + ";"; }
|
||||
};
|
||||
string.appendToBuffer = true;
|
||||
return string;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -78,16 +88,20 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
var opcodes = environment.opcodes,
|
||||
opcode,
|
||||
firstLoc,
|
||||
i,
|
||||
l;
|
||||
|
||||
for (i = 0, l = opcodes.length; i < l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
this.source.currentLocation = opcode.loc;
|
||||
firstLoc = firstLoc || opcode.loc;
|
||||
this[opcode.opcode].apply(this, opcode.args);
|
||||
}
|
||||
|
||||
// Flush any trailing content that might be pending.
|
||||
this.source.currentLocation = firstLoc;
|
||||
this.pushSource('');
|
||||
|
||||
/* istanbul ignore next */
|
||||
@@ -123,7 +137,16 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
if (!asObject) {
|
||||
ret.compiler = JSON.stringify(ret.compiler);
|
||||
|
||||
this.source.currentLocation = {firstLine: 1, firstColumn: 0};
|
||||
ret = this.objectLiteral(ret);
|
||||
|
||||
if (options.srcName) {
|
||||
ret = ret.toStringWithSourceMap({file: options.destName});
|
||||
ret.map = ret.map && ret.map.toString();
|
||||
} else {
|
||||
ret = ret.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -136,7 +159,7 @@ JavaScriptCompiler.prototype = {
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = [];
|
||||
this.source = new CodeGen(this.options.srcName);
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
@@ -168,59 +191,67 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
return Function.apply(this, params);
|
||||
} else {
|
||||
return 'function(' + params.join(',') + ') {\n ' + source + '}';
|
||||
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
|
||||
}
|
||||
},
|
||||
mergeSource: function(varDeclarations) {
|
||||
var source = '',
|
||||
buffer,
|
||||
var isSimple = this.environment.isSimple,
|
||||
appendOnly = !this.forceBuffer,
|
||||
appendFirst;
|
||||
appendFirst,
|
||||
|
||||
for (var i = 0, len = this.source.length; i < len; i++) {
|
||||
var line = this.source[i];
|
||||
sourceSeen,
|
||||
bufferStart,
|
||||
bufferEnd;
|
||||
this.source.each(function(line) {
|
||||
if (line.appendToBuffer) {
|
||||
if (buffer) {
|
||||
buffer = buffer + '\n + ' + line.content;
|
||||
if (bufferStart) {
|
||||
line.prepend(' + ');
|
||||
} else {
|
||||
buffer = line.content;
|
||||
bufferStart = line;
|
||||
}
|
||||
bufferEnd = line;
|
||||
} else {
|
||||
if (buffer) {
|
||||
if (!source) {
|
||||
if (bufferStart) {
|
||||
if (!sourceSeen) {
|
||||
appendFirst = true;
|
||||
source = buffer + ';\n ';
|
||||
} else {
|
||||
source += 'buffer += ' + buffer + ';\n ';
|
||||
bufferStart.prepend('buffer += ');
|
||||
}
|
||||
buffer = undefined;
|
||||
bufferEnd.add(';');
|
||||
bufferStart = bufferEnd = undefined;
|
||||
}
|
||||
source += line + '\n ';
|
||||
|
||||
if (!this.environment.isSimple) {
|
||||
sourceSeen = true;
|
||||
if (!isSimple) {
|
||||
appendOnly = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (appendOnly) {
|
||||
if (buffer || !source) {
|
||||
source += 'return ' + (buffer || '""') + ';\n';
|
||||
if (bufferStart) {
|
||||
bufferStart.prepend('return ');
|
||||
bufferEnd.add(';');
|
||||
} else {
|
||||
this.source.push('return "";');
|
||||
}
|
||||
} else {
|
||||
varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
|
||||
if (buffer) {
|
||||
source += 'return buffer + ' + buffer + ';\n';
|
||||
|
||||
if (bufferStart) {
|
||||
bufferStart.prepend('return buffer + ');
|
||||
bufferEnd.add(';');
|
||||
} else {
|
||||
source += 'return buffer;\n';
|
||||
this.source.push('return buffer;');
|
||||
}
|
||||
}
|
||||
|
||||
if (varDeclarations) {
|
||||
source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n ') + source;
|
||||
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n '));
|
||||
}
|
||||
|
||||
return source;
|
||||
return this.source.merge();
|
||||
},
|
||||
|
||||
// [blockValue]
|
||||
@@ -241,7 +272,7 @@ JavaScriptCompiler.prototype = {
|
||||
var blockName = this.popStack();
|
||||
params.splice(1, 0, blockName);
|
||||
|
||||
this.push('blockHelperMissing.call(' + params.join(', ') + ')');
|
||||
this.push(this.source.functionCall('blockHelperMissing', 'call', params));
|
||||
},
|
||||
|
||||
// [ambiguousBlockValue]
|
||||
@@ -262,7 +293,10 @@ JavaScriptCompiler.prototype = {
|
||||
var current = this.topStack();
|
||||
params.splice(1, 0, current);
|
||||
|
||||
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
|
||||
this.pushSource([
|
||||
'if (!', this.lastHelper, ') { ',
|
||||
current, ' = ', this.source.functionCall('blockHelperMissing', 'call', params),
|
||||
'}']);
|
||||
},
|
||||
|
||||
// [appendContent]
|
||||
@@ -274,6 +308,8 @@ JavaScriptCompiler.prototype = {
|
||||
appendContent: function(content) {
|
||||
if (this.pendingContent) {
|
||||
content = this.pendingContent + content;
|
||||
} else {
|
||||
this.pendingLocation = this.source.currentLocation;
|
||||
}
|
||||
|
||||
this.pendingContent = content;
|
||||
@@ -291,15 +327,15 @@ JavaScriptCompiler.prototype = {
|
||||
append: function() {
|
||||
if (this.isInline()) {
|
||||
this.replaceStack(function(current) {
|
||||
return ' != null ? ' + current + ' : ""';
|
||||
return [' != null ? ', current, ' : ""'];
|
||||
});
|
||||
|
||||
this.pushSource(this.appendToBuffer(this.popStack()));
|
||||
} else {
|
||||
var local = this.popStack();
|
||||
this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }');
|
||||
this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
|
||||
if (this.environment.isSimple) {
|
||||
this.pushSource("else { " + this.appendToBuffer("''") + " }");
|
||||
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -313,7 +349,7 @@ JavaScriptCompiler.prototype = {
|
||||
appendEscaped: function() {
|
||||
this.aliases.escapeExpression = 'this.escapeExpression';
|
||||
|
||||
this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
|
||||
this.pushSource(this.appendToBuffer(['escapeExpression(', this.popStack(), ')']));
|
||||
},
|
||||
|
||||
// [getContext]
|
||||
@@ -363,10 +399,10 @@ JavaScriptCompiler.prototype = {
|
||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
||||
// needs to have the special handling for these values.
|
||||
if (!falsy) {
|
||||
return ' != null ? ' + lookup + ' : ' + current;
|
||||
return [' != null ? ', lookup, ' : ', current];
|
||||
} else {
|
||||
// Otherwise we can use generic falsy handling
|
||||
return ' && ' + lookup;
|
||||
return [' && ', lookup];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -389,7 +425,7 @@ JavaScriptCompiler.prototype = {
|
||||
var len = parts.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
this.replaceStack(function(current) {
|
||||
return ' && ' + this.nameLookup(current, parts[i], 'data');
|
||||
return [' && ', this.nameLookup(current, parts[i], 'data')];
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -404,7 +440,7 @@ JavaScriptCompiler.prototype = {
|
||||
resolvePossibleLambda: function() {
|
||||
this.aliases.lambda = 'this.lambda';
|
||||
|
||||
this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')');
|
||||
this.push(['lambda(', this.popStack(), ', ', this.contextName(0), ')']);
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
@@ -452,14 +488,14 @@ JavaScriptCompiler.prototype = {
|
||||
this.hash = this.hashes.pop();
|
||||
|
||||
if (this.trackIds) {
|
||||
this.push('{' + hash.ids.join(',') + '}');
|
||||
this.push(this.objectLiteral(hash.ids));
|
||||
}
|
||||
if (this.stringParams) {
|
||||
this.push('{' + hash.contexts.join(',') + '}');
|
||||
this.push('{' + hash.types.join(',') + '}');
|
||||
this.push(this.objectLiteral(hash.contexts));
|
||||
this.push(this.objectLiteral(hash.types));
|
||||
}
|
||||
|
||||
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
|
||||
this.push(this.objectLiteral(hash.values));
|
||||
},
|
||||
|
||||
// [pushString]
|
||||
@@ -472,17 +508,6 @@ JavaScriptCompiler.prototype = {
|
||||
this.pushStackLiteral(this.quotedString(string));
|
||||
},
|
||||
|
||||
// [push]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: expr, ...
|
||||
//
|
||||
// Push an expression onto the stack
|
||||
push: function(expr) {
|
||||
this.inlineStack.push(expr);
|
||||
return expr;
|
||||
},
|
||||
|
||||
// [pushLiteral]
|
||||
//
|
||||
// On stack, before: ...
|
||||
@@ -525,9 +550,13 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
var nonHelper = this.popStack();
|
||||
var helper = this.setupHelper(paramSize, name);
|
||||
var simple = isSimple ? [helper.name, ' || '] : '';
|
||||
|
||||
var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing';
|
||||
this.push('((' + lookup + ').call(' + helper.callParams + '))');
|
||||
this.push(
|
||||
this.source.functionCall(
|
||||
['('].concat(simple, nonHelper, ' || ', 'helperMissing)'),
|
||||
'call',
|
||||
helper.callParams));
|
||||
},
|
||||
|
||||
// [invokeKnownHelper]
|
||||
@@ -539,7 +568,7 @@ JavaScriptCompiler.prototype = {
|
||||
// so a `helperMissing` fallback is not required.
|
||||
invokeKnownHelper: function(paramSize, name) {
|
||||
var helper = this.setupHelper(paramSize, name);
|
||||
this.push(helper.name + ".call(" + helper.callParams + ")");
|
||||
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
|
||||
},
|
||||
|
||||
// [invokeAmbiguous]
|
||||
@@ -566,10 +595,12 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
this.push(
|
||||
'((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing'
|
||||
+ (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),'
|
||||
+ '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))');
|
||||
this.push([
|
||||
'((helper = (helper = ', helperName, ' || ', nonHelper, ') != null ? helper : helperMissing',
|
||||
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
|
||||
'(typeof helper === functionType ? ',
|
||||
this.source.functionCall('helper','call', helper.callParams), ' : helper))'
|
||||
]);
|
||||
},
|
||||
|
||||
// [invokePartial]
|
||||
@@ -591,7 +622,7 @@ JavaScriptCompiler.prototype = {
|
||||
params.push('depths');
|
||||
}
|
||||
|
||||
this.push("this.invokePartial(" + params.join(", ") + ")");
|
||||
this.push(this.source.functionCall('this.invokePartial', '', params));
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
@@ -616,15 +647,15 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
var hash = this.hash;
|
||||
if (context) {
|
||||
hash.contexts.push("'" + key + "': " + context);
|
||||
hash.contexts[key] = context;
|
||||
}
|
||||
if (type) {
|
||||
hash.types.push("'" + key + "': " + type);
|
||||
hash.types[key] = type;
|
||||
}
|
||||
if (id) {
|
||||
hash.ids.push("'" + key + "': " + id);
|
||||
hash.ids[key] = id;
|
||||
}
|
||||
hash.values.push("'" + key + "': (" + value + ")");
|
||||
hash.values[key] = value;
|
||||
},
|
||||
|
||||
pushId: function(type, name) {
|
||||
@@ -696,13 +727,23 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
push: function(expr) {
|
||||
if (!(expr instanceof Literal)) {
|
||||
expr = this.source.wrap(expr);
|
||||
}
|
||||
|
||||
this.inlineStack.push(expr);
|
||||
return expr;
|
||||
},
|
||||
|
||||
pushStackLiteral: function(item) {
|
||||
this.push(new Literal(item));
|
||||
},
|
||||
|
||||
pushSource: function(source) {
|
||||
if (this.pendingContent) {
|
||||
this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
|
||||
this.source.push(
|
||||
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
|
||||
this.pendingContent = undefined;
|
||||
}
|
||||
|
||||
@@ -712,7 +753,7 @@ JavaScriptCompiler.prototype = {
|
||||
},
|
||||
|
||||
replaceStack: function(callback) {
|
||||
var prefix = '',
|
||||
var prefix = ['('],
|
||||
inline = this.isInline(),
|
||||
stack,
|
||||
createdStack,
|
||||
@@ -728,14 +769,15 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
if (top instanceof Literal) {
|
||||
// Literals do not need to be inlined
|
||||
prefix = stack = top.value;
|
||||
stack = [top.value];
|
||||
prefix = ['(', stack];
|
||||
usedLiteral = true;
|
||||
} else {
|
||||
// Get or create the current stack name for use by the inline
|
||||
createdStack = true;
|
||||
var name = this.incrStack();
|
||||
|
||||
prefix = '(' + this.push(name) + ' = ' + top + ')';
|
||||
prefix = ['((', this.push(name), ' = ', top, ')'];
|
||||
stack = this.topStack();
|
||||
}
|
||||
|
||||
@@ -747,7 +789,7 @@ JavaScriptCompiler.prototype = {
|
||||
if (createdStack) {
|
||||
this.stackSlot--;
|
||||
}
|
||||
this.push('(' + prefix + item + ')');
|
||||
this.push(prefix.concat(item, ')'));
|
||||
},
|
||||
|
||||
incrStack: function() {
|
||||
@@ -768,7 +810,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.compileStack.push(entry);
|
||||
} else {
|
||||
var stack = this.incrStack();
|
||||
this.pushSource(stack + " = " + entry + ";");
|
||||
this.pushSource([stack, ' = ', entry, ';']);
|
||||
this.compileStack.push(stack);
|
||||
}
|
||||
}
|
||||
@@ -816,25 +858,11 @@ JavaScriptCompiler.prototype = {
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
return this.source.quotedString(str);
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
var pairs = [];
|
||||
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
pairs.push(this.quotedString(key) + ':' + obj[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return '{' + pairs.join(',') + '}';
|
||||
return this.source.objectLiteral(obj);
|
||||
},
|
||||
|
||||
setupHelper: function(paramSize, name, blockHelper) {
|
||||
@@ -846,7 +874,7 @@ JavaScriptCompiler.prototype = {
|
||||
params: params,
|
||||
paramsInit: paramsInit,
|
||||
name: foundHelper,
|
||||
callParams: [this.contextName(0)].concat(params).join(", ")
|
||||
callParams: [this.contextName(0)].concat(params)
|
||||
};
|
||||
},
|
||||
|
||||
@@ -891,11 +919,11 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
|
||||
if (this.trackIds) {
|
||||
options.ids = "[" + ids.join(",") + "]";
|
||||
options.ids = this.source.generateArray(ids);
|
||||
}
|
||||
if (this.stringParams) {
|
||||
options.types = "[" + types.join(",") + "]";
|
||||
options.contexts = "[" + contexts.join(",") + "]";
|
||||
options.types = this.source.generateArray(types);
|
||||
options.contexts = this.source.generateArray(contexts);
|
||||
}
|
||||
|
||||
if (this.options.data) {
|
||||
@@ -906,7 +934,7 @@ JavaScriptCompiler.prototype = {
|
||||
if (useRegister) {
|
||||
this.useRegister('options');
|
||||
params.push('options');
|
||||
return 'options=' + options;
|
||||
return ['options=', options];
|
||||
} else {
|
||||
params.push(options);
|
||||
return '';
|
||||
@@ -914,6 +942,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var reservedWords = (
|
||||
"break else new var" +
|
||||
" case finally return void" +
|
||||
|
||||
+57
-20
@@ -2,6 +2,9 @@
|
||||
var fs = require('fs'),
|
||||
Handlebars = require('./index'),
|
||||
basename = require('path').basename,
|
||||
SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer,
|
||||
SourceNode = SourceMap.SourceNode,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
@@ -45,20 +48,23 @@ module.exports.cli = function(opts) {
|
||||
var extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
var output = [];
|
||||
var output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.push('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (opts.commonjs) {
|
||||
output.push('var Handlebars = require("' + opts.commonjs + '");');
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
output.push('(function() {\n');
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
output.push(' var template = Handlebars.template, templates = ');
|
||||
output.push(opts.namespace);
|
||||
output.push(' = ');
|
||||
output.push(opts.namespace);
|
||||
output.push(' || {};\n');
|
||||
output.add(' var template = Handlebars.template, templates = ');
|
||||
if (opts.namespace) {
|
||||
output.add(opts.namespace);
|
||||
output.add(' = ');
|
||||
output.add(opts.namespace);
|
||||
output.add(' || ');
|
||||
}
|
||||
output.add('{};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
var path = template,
|
||||
@@ -83,6 +89,9 @@ module.exports.cli = function(opts) {
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
@@ -95,18 +104,26 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
var precompiled = Handlebars.precompile(data, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
var consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.push(Handlebars.precompile(data, options) + '\n');
|
||||
output.add([precompiled, '\n']);
|
||||
} else if (opts.partial) {
|
||||
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
output.add('return ');
|
||||
}
|
||||
output.push('Handlebars.partials[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
|
||||
output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
} else {
|
||||
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
output.add('return ');
|
||||
}
|
||||
output.push('templates[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
|
||||
output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,22 +137,42 @@ module.exports.cli = function(opts) {
|
||||
if (opts.amd) {
|
||||
if(opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if(opts.partial){
|
||||
output.push('return Handlebars.partials;\n');
|
||||
output.add('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.push('return templates;\n');
|
||||
output.add('return templates;\n');
|
||||
}
|
||||
}
|
||||
output.push('});');
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
output.push('})();');
|
||||
output.add('})();');
|
||||
}
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
|
||||
if (opts.map) {
|
||||
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
||||
}
|
||||
|
||||
output = output.toStringWithSourceMap();
|
||||
output.map = output.map + '';
|
||||
|
||||
if (opts.min) {
|
||||
output = uglify.minify(output, {fromString: true}).code;
|
||||
output = uglify.minify(output.code, {
|
||||
fromString: true,
|
||||
|
||||
outSourceMap: opts.map,
|
||||
inSourceMap: JSON.parse(output.map)
|
||||
});
|
||||
if (opts.map) {
|
||||
output.code += '\n//# sourceMappingURL=' + opts.map + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
fs.writeFileSync(opts.map, output.map, 'utf8');
|
||||
}
|
||||
output = output.code;
|
||||
|
||||
if (opts.output) {
|
||||
fs.writeFileSync(opts.output, output, 'utf8');
|
||||
} else {
|
||||
|
||||
+2
-1
@@ -21,7 +21,8 @@
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"optimist": "~0.3"
|
||||
"optimist": "~0.3",
|
||||
"source-map": "^0.1.40"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "~2.3"
|
||||
|
||||
Vendored
+2
@@ -9,6 +9,8 @@ global.Handlebars = undefined;
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
|
||||
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
return "";
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
|
||||
@@ -60,6 +60,10 @@ describe('helpers', function() {
|
||||
}};
|
||||
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
|
||||
});
|
||||
it('helper returning undefined value', function() {
|
||||
shouldCompileTo(' {{nothere}}', [{}, {nothere: function() {}}], ' ');
|
||||
shouldCompileTo(' {{#nothere}}{{/nothere}}', [{}, {nothere: function() {}}], ' ');
|
||||
});
|
||||
|
||||
it("block helper", function() {
|
||||
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('javascript-compiler api', function() {
|
||||
});
|
||||
it('should allow append buffer override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
|
||||
return $superAppend.call(this, string + ' + "_foo"');
|
||||
return $superAppend.call(this, [string, ' + "_foo"']);
|
||||
};
|
||||
shouldCompileTo("{{foo}}", { foo: "food" }, "food_foo");
|
||||
});
|
||||
|
||||
+15
-1
@@ -98,7 +98,8 @@ describe('precompiler', function() {
|
||||
});
|
||||
it('should output multiple amd', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars', namespace: 'foo'});
|
||||
equal(/templates = foo = foo \|\|/.test(log), true);
|
||||
equal(/return templates/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
@@ -156,4 +157,17 @@ describe('precompiler', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'});
|
||||
equal(log, 'min');
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
fs.writeFileSync = function(_file, _content) {
|
||||
console.error(arguments);
|
||||
file = _file;
|
||||
content = _content;
|
||||
};
|
||||
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], map: 'foo.js.map', extension: 'handlebars'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*global CompilerContext, Handlebars */
|
||||
var SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
|
||||
describe('source-map', function() {
|
||||
if (!Handlebars.precompile) {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should safely include source map info', function() {
|
||||
var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
|
||||
|
||||
equal(!!template.code, true);
|
||||
equal(!!template.map, !CompilerContext.browser);
|
||||
});
|
||||
it('should map source properly', function() {
|
||||
var source = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
|
||||
template = Handlebars.precompile(source, {destName: 'dest.js', srcName: 'src.hbs'});
|
||||
|
||||
if (template.map) {
|
||||
var consumer = new SourceMapConsumer(template.map),
|
||||
lines = template.code.split('\n'),
|
||||
srcLines = source.split('\n'),
|
||||
|
||||
generated = grepLine('" b"', lines),
|
||||
source = grepLine(' b', srcLines);
|
||||
|
||||
var mapped = consumer.originalPositionFor(generated);
|
||||
equal(mapped.line, source.line);
|
||||
equal(mapped.column, source.column);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function grepLine(token, lines) {
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var column = lines[i].indexOf(token);
|
||||
if (column >= 0) {
|
||||
return {
|
||||
line: i+1,
|
||||
column: column
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user