react / wstein / node_modules / jest-cli / node_modules / istanbul / node_modules / handlebars / lib / handlebars / compiler / compiler.js
80713 viewsimport Exception from "../exception";1import {isArray, indexOf} from "../utils";2import AST from "./ast";34var slice = [].slice;567export function Compiler() {}89// the foundHelper register will disambiguate helper lookup from finding a10// function in a context. This is necessary for mustache compatibility, which11// requires that context functions in blocks are evaluated by blockHelperMissing,12// and then proceed as if the resulting value was provided to blockHelperMissing.1314Compiler.prototype = {15compiler: Compiler,1617equals: function(other) {18var len = this.opcodes.length;19if (other.opcodes.length !== len) {20return false;21}2223for (var i = 0; i < len; i++) {24var opcode = this.opcodes[i],25otherOpcode = other.opcodes[i];26if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {27return false;28}29}3031// We know that length is the same between the two arrays because they are directly tied32// to the opcode behavior above.33len = this.children.length;34for (i = 0; i < len; i++) {35if (!this.children[i].equals(other.children[i])) {36return false;37}38}3940return true;41},4243guid: 0,4445compile: function(program, options) {46this.sourceNode = [];47this.opcodes = [];48this.children = [];49this.options = options;50this.stringParams = options.stringParams;51this.trackIds = options.trackIds;5253options.blockParams = options.blockParams || [];5455// These changes will propagate to the other compiler components56var knownHelpers = options.knownHelpers;57options.knownHelpers = {58'helperMissing': true,59'blockHelperMissing': true,60'each': true,61'if': true,62'unless': true,63'with': true,64'log': true,65'lookup': true66};67if (knownHelpers) {68for (var name in knownHelpers) {69options.knownHelpers[name] = knownHelpers[name];70}71}7273return this.accept(program);74},7576compileProgram: function(program) {77var result = new this.compiler().compile(program, this.options);78var guid = this.guid++;7980this.usePartial = this.usePartial || result.usePartial;8182this.children[guid] = result;83this.useDepths = this.useDepths || result.useDepths;8485return guid;86},8788accept: function(node) {89this.sourceNode.unshift(node);90var ret = this[node.type](node);91this.sourceNode.shift();92return ret;93},9495Program: function(program) {96this.options.blockParams.unshift(program.blockParams);9798var body = program.body;99for(var i=0, l=body.length; i<l; i++) {100this.accept(body[i]);101}102103this.options.blockParams.shift();104105this.isSimple = l === 1;106this.blockParams = program.blockParams ? program.blockParams.length : 0;107108return this;109},110111BlockStatement: function(block) {112transformLiteralToPath(block);113114var program = block.program,115inverse = block.inverse;116117program = program && this.compileProgram(program);118inverse = inverse && this.compileProgram(inverse);119120var type = this.classifySexpr(block);121122if (type === 'helper') {123this.helperSexpr(block, program, inverse);124} else if (type === 'simple') {125this.simpleSexpr(block);126127// now that the simple mustache is resolved, we need to128// evaluate it by executing `blockHelperMissing`129this.opcode('pushProgram', program);130this.opcode('pushProgram', inverse);131this.opcode('emptyHash');132this.opcode('blockValue', block.path.original);133} else {134this.ambiguousSexpr(block, program, inverse);135136// now that the simple mustache is resolved, we need to137// evaluate it by executing `blockHelperMissing`138this.opcode('pushProgram', program);139this.opcode('pushProgram', inverse);140this.opcode('emptyHash');141this.opcode('ambiguousBlockValue');142}143144this.opcode('append');145},146147PartialStatement: function(partial) {148this.usePartial = true;149150var params = partial.params;151if (params.length > 1) {152throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);153} else if (!params.length) {154params.push({type: 'PathExpression', parts: [], depth: 0});155}156157var partialName = partial.name.original,158isDynamic = partial.name.type === 'SubExpression';159if (isDynamic) {160this.accept(partial.name);161}162163this.setupFullMustacheParams(partial, undefined, undefined, true);164165var indent = partial.indent || '';166if (this.options.preventIndent && indent) {167this.opcode('appendContent', indent);168indent = '';169}170171this.opcode('invokePartial', isDynamic, partialName, indent);172this.opcode('append');173},174175MustacheStatement: function(mustache) {176this.SubExpression(mustache);177178if(mustache.escaped && !this.options.noEscape) {179this.opcode('appendEscaped');180} else {181this.opcode('append');182}183},184185ContentStatement: function(content) {186if (content.value) {187this.opcode('appendContent', content.value);188}189},190191CommentStatement: function() {},192193SubExpression: function(sexpr) {194transformLiteralToPath(sexpr);195var type = this.classifySexpr(sexpr);196197if (type === 'simple') {198this.simpleSexpr(sexpr);199} else if (type === 'helper') {200this.helperSexpr(sexpr);201} else {202this.ambiguousSexpr(sexpr);203}204},205ambiguousSexpr: function(sexpr, program, inverse) {206var path = sexpr.path,207name = path.parts[0],208isBlock = program != null || inverse != null;209210this.opcode('getContext', path.depth);211212this.opcode('pushProgram', program);213this.opcode('pushProgram', inverse);214215this.accept(path);216217this.opcode('invokeAmbiguous', name, isBlock);218},219220simpleSexpr: function(sexpr) {221this.accept(sexpr.path);222this.opcode('resolvePossibleLambda');223},224225helperSexpr: function(sexpr, program, inverse) {226var params = this.setupFullMustacheParams(sexpr, program, inverse),227path = sexpr.path,228name = path.parts[0];229230if (this.options.knownHelpers[name]) {231this.opcode('invokeKnownHelper', params.length, name);232} else if (this.options.knownHelpersOnly) {233throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);234} else {235path.falsy = true;236237this.accept(path);238this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));239}240},241242PathExpression: function(path) {243this.addDepth(path.depth);244this.opcode('getContext', path.depth);245246var name = path.parts[0],247scoped = AST.helpers.scopedId(path),248blockParamId = !path.depth && !scoped && this.blockParamIndex(name);249250if (blockParamId) {251this.opcode('lookupBlockParam', blockParamId, path.parts);252} else if (!name) {253// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`254this.opcode('pushContext');255} else if (path.data) {256this.options.data = true;257this.opcode('lookupData', path.depth, path.parts);258} else {259this.opcode('lookupOnContext', path.parts, path.falsy, scoped);260}261},262263StringLiteral: function(string) {264this.opcode('pushString', string.value);265},266267NumberLiteral: function(number) {268this.opcode('pushLiteral', number.value);269},270271BooleanLiteral: function(bool) {272this.opcode('pushLiteral', bool.value);273},274275Hash: function(hash) {276var pairs = hash.pairs, i, l;277278this.opcode('pushHash');279280for (i=0, l=pairs.length; i<l; i++) {281this.pushParam(pairs[i].value);282}283while (i--) {284this.opcode('assignToHash', pairs[i].key);285}286this.opcode('popHash');287},288289// HELPERS290opcode: function(name) {291this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });292},293294addDepth: function(depth) {295if (!depth) {296return;297}298299this.useDepths = true;300},301302classifySexpr: function(sexpr) {303var isSimple = AST.helpers.simpleId(sexpr.path);304305var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);306307// a mustache is an eligible helper if:308// * its id is simple (a single part, not `this` or `..`)309var isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);310311// if a mustache is an eligible helper but not a definite312// helper, it is ambiguous, and will be resolved in a later313// pass or at runtime.314var isEligible = !isBlockParam && (isHelper || isSimple);315316var options = this.options;317318// if ambiguous, we can possibly resolve the ambiguity now319// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.320if (isEligible && !isHelper) {321var name = sexpr.path.parts[0];322323if (options.knownHelpers[name]) {324isHelper = true;325} else if (options.knownHelpersOnly) {326isEligible = false;327}328}329330if (isHelper) { return 'helper'; }331else if (isEligible) { return 'ambiguous'; }332else { return 'simple'; }333},334335pushParams: function(params) {336for(var i=0, l=params.length; i<l; i++) {337this.pushParam(params[i]);338}339},340341pushParam: function(val) {342var value = val.value != null ? val.value : val.original || '';343344if (this.stringParams) {345if (value.replace) {346value = value347.replace(/^(\.?\.\/)*/g, '')348.replace(/\//g, '.');349}350351if(val.depth) {352this.addDepth(val.depth);353}354this.opcode('getContext', val.depth || 0);355this.opcode('pushStringParam', value, val.type);356357if (val.type === 'SubExpression') {358// SubExpressions get evaluated and passed in359// in string params mode.360this.accept(val);361}362} else {363if (this.trackIds) {364var blockParamIndex;365if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {366blockParamIndex = this.blockParamIndex(val.parts[0]);367}368if (blockParamIndex) {369var blockParamChild = val.parts.slice(1).join('.');370this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);371} else {372value = val.original || value;373if (value.replace) {374value = value375.replace(/^\.\//g, '')376.replace(/^\.$/g, '');377}378379this.opcode('pushId', val.type, value);380}381}382this.accept(val);383}384},385386setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {387var params = sexpr.params;388this.pushParams(params);389390this.opcode('pushProgram', program);391this.opcode('pushProgram', inverse);392393if (sexpr.hash) {394this.accept(sexpr.hash);395} else {396this.opcode('emptyHash', omitEmpty);397}398399return params;400},401402blockParamIndex: function(name) {403for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) {404var blockParams = this.options.blockParams[depth],405param = blockParams && indexOf(blockParams, name);406if (blockParams && param >= 0) {407return [depth, param];408}409}410}411};412413export function precompile(input, options, env) {414if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {415throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);416}417418options = options || {};419if (!('data' in options)) {420options.data = true;421}422if (options.compat) {423options.useDepths = true;424}425426var ast = env.parse(input, options);427var environment = new env.Compiler().compile(ast, options);428return new env.JavaScriptCompiler().compile(environment, options);429}430431export function compile(input, options, env) {432if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {433throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);434}435436options = options || {};437438if (!('data' in options)) {439options.data = true;440}441if (options.compat) {442options.useDepths = true;443}444445var compiled;446447function compileInput() {448var ast = env.parse(input, options);449var environment = new env.Compiler().compile(ast, options);450var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);451return env.template(templateSpec);452}453454// Template is only compiled on first use and cached after that point.455var ret = function(context, options) {456if (!compiled) {457compiled = compileInput();458}459return compiled.call(this, context, options);460};461ret._setup = function(options) {462if (!compiled) {463compiled = compileInput();464}465return compiled._setup(options);466};467ret._child = function(i, data, blockParams, depths) {468if (!compiled) {469compiled = compileInput();470}471return compiled._child(i, data, blockParams, depths);472};473return ret;474}475476function argEquals(a, b) {477if (a === b) {478return true;479}480481if (isArray(a) && isArray(b) && a.length === b.length) {482for (var i = 0; i < a.length; i++) {483if (!argEquals(a[i], b[i])) {484return false;485}486}487return true;488}489}490491function transformLiteralToPath(sexpr) {492if (!sexpr.path.parts) {493var literal = sexpr.path;494// Casting to string here to make false and 0 literal values play nicely with the rest495// of the system.496sexpr.path = new AST.PathExpression(false, 0, [literal.original+''], literal.original+'', literal.log);497}498}499500501