Loading the parity record
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Parity / Handlebars
Every number on this page was produced by running both implementations over the same cases: the real handlebars package pinned at 4.7.8 answers first, and its answer is the expectation the Go port is held to. Nothing is a hand-written expectation, so a new upstream release re-scores the port on its own. See Handlebars for the port's own documentation. Source: github.com/malcolmston/handlebars.
Not a generic diagram: every node below names an artefact of this harness — the pinned package it installed, the runner files it started, the case files it streamed, and the counts it wrote out.
| Case group | Cases | Match | Mismatch | Group parity |
|---|---|---|---|---|
| values | 51 | 38 | 13 | 74.5% |
| builtins | 43 | 41 | 2 | 95.3% |
| syntax | 31 | 27 | 4 | 87.1% |
| partials | 25 | 23 | 2 | 92.0% |
| helpers-custom | 21 | 20 | 1 | 95.2% |
| interpolation | 21 | 21 | 0 | 100.0% |
| options | 19 | 16 | 3 | 84.2% |
| paths | 16 | 12 | 4 | 75.0% |
| blockparams | 9 | 9 | 0 | 100.0% |
Every exported symbol of the upstream package, and what the port offers for it. The upstream list is derived mechanically, never from a README: cd parity/handlebars/node
node -e "const H=require('handlebars');
console.log('TOP:', Object.keys(H).sort().join(' '));
console.log('HELPERS:', Object.keys(H.helpers).sort().join(' '));
console.log('DECORATORS:', Object.keys(H.decorators).sort().join(' '));
console.log('UTILS:', Object.keys(H.Utils).sort().join(' '));
console.log('PARTIALS:', Object.keys(H.partials).sort().join(' '));". A symbol with no case is untested, never a match.
| Upstream symbol | Go symbol | Status | Cases | Note |
|---|---|---|---|---|
| Handlebars.AST | handlebars.Program, Node, BlockNode, MustacheNode, PartialNode, DecoratorNode, CommentNode, ContentNode, Expr, Path, HashPair | untested | — | node types are exported but there are no AST builders/helpers and the harness compares rendered output, not trees |
| Handlebars.COMPILER_REVISION | — | missing | — | no precompiled-template format in the port |
| Handlebars.Compiler | — | missing | — | no compile-to-JS stage |
| Handlebars.Exception | — | missing | — | the port returns plain error values; message text differs everywhere and is never compared |
| Handlebars.HandlebarsEnvironment | handlebars.New, handlebars.Template.Clone | untested | — | isolated registries exist, but there is no environment object to compare directly |
| Handlebars.JavaScriptCompiler | — | missing | — | |
| Handlebars.LAST_COMPATIBLE_COMPILER_REVISION | — | missing | — | |
| Handlebars.Parser | — | missing | — | jison parser object; the port parses internally via Parse/Compile |
| Handlebars.PrintVisitor | — | missing | — | |
| Handlebars.REVISION_CHANGES | — | missing | — | |
| Handlebars.SafeString | handlebars.SafeString | match | utils-safestring, helper-inline-safestring, helper-inline-safestring-triple | |
| Handlebars.Utils | (package-level funcs) | match | see the Utils table | |
| Handlebars.VERSION | — | missing | — | the repo has a VERSION file but exports no constant |
| Handlebars.VM | — | missing | — | invokePartial/resolvePartial are internal in the port; behaviour is covered by the partial-* cases |
| Handlebars.Visitor | — | missing | — | |
| Handlebars.__esModule | — | missing | — | CommonJS/ESM interop artefact, not API |
| Handlebars.compile | handlebars.Compile | differs | var-*, path-*, builtins/syntax groups (200+ cases) | upstream is a lazy compile (parse happens on first call); the port parses eagerly. Parser strictness differs — see Divergences |
| Handlebars.create | handlebars.New, handlebars.Template.Clone | untested | — | no case isolates registry cloning |
| Handlebars.createFrame | handlebars.CreateFrame | match | utils-createframe, utils-createframe-empty | |
| Handlebars.decorators | handlebars.Template.RegisterDecorator / UnregisterDecorator | match | decorator-registered-noop-block, decorator-registered-noop-inline, decorator-unknown-throws, decorator-inline-no-name-throws | signatures differ (Decorator func(*DecoratorOptions) cannot replace the program function, so only side-effecting decorators are comparable) |
| Handlebars.default | — | missing | — | interop artefact, not API |
| Handlebars.escapeExpression | handlebars.EscapeExpression | differs | esc-expr-string, esc-expr-number, esc-expr-null, esc-expr-false, esc-expr-true, esc-expr-empty-string, esc-expr-array, esc-expr-object | strings/numbers/bools/null match; objects and arrays do not |
| Handlebars.helpers | handlebars.Template.RegisterHelper(s) / UnregisterHelper / HasHelper / HelperNames | match | helpers-custom group (21 cases) | |
| Handlebars.log | — | missing | — | no package-level log(); the port has Template.SetLogger(*log.Logger) |
| Handlebars.logger | — | missing | — | no logger object / logger.level |
| Handlebars.noConflict | — | missing | — | browser global helper, not meaningful in Go |
| Handlebars.parse | handlebars.Parse | differs | compile-only-valid, compile-only-invalid | upstream returns an AST Program; the port returns a compiled *Template |
| Handlebars.parseWithoutProcessing | — | missing | — | no way to parse without the standalone-whitespace pass |
| Handlebars.partials | handlebars.Template.RegisterPartial(s) / UnregisterPartial / HasPartial / PartialNames | match | partials group (25 cases) | |
| Handlebars.precompile | — | missing | — | no precompilation; used here only as the oracle that forces a parse |
| Handlebars.print | — | missing | — | |
| Handlebars.template | — | missing | — | no runtime for precompiled specs |
| helpers.blockHelperMissing | RegisterHelper("blockHelperMissing") | match | block-helper-missing-hook, inverse-section, inverse-section-truthy, mustache-section-object, mustache-section-array, mustache-section-empty-array | |
| helpers.each | built into the renderer | differs | each-array, each-data-vars, each-object-key, each-object-insertion-order, each-object-index, each-empty-else, each-missing-else, each-null, each-scalar, each-nested-objects, each-nested-parent-index, bp-each-* | only divergence is map iteration order (each-object-insertion-order) |
| helpers.helperMissing | RegisterHelper("helperMissing") | match | helper-missing-hook-bare, helper-missing-hook-with-args, opt-nonstrict-unknown-bare-empty, opt-nondstrict-unknown-helper-with-args-throws | |
| helpers.if | built into the renderer | match | if-true, if-else, if-falsy-empty-string, if-falsy-zero, if-include-zero, if-falsy-empty-array, if-truthy-empty-object, if-missing, if-null, if-chained-else-if, if-chained-else-if-fallthrough, if-chained-else-unless, if-chained-else-with | includeZero hash supported |
| helpers.log | RegisterHelper("log") + Template.SetLogger | match | log-helper, log-helper-with-level | output is empty on both sides; the log line itself is not compared |
| helpers.lookup | RegisterHelper("lookup") | match | lookup-by-key, lookup-array-index, lookup-missing, lookup-null-target, subexpr-lookup, partial-dynamic-lookup, partial-dynamic-lookup-self | |
| helpers.unless | built into the renderer | match | unless-basic, unless-else, if-chained-else-unless | |
| helpers.with | built into the renderer | match | with-object, with-falsy-else, with-missing, with-scalar, bp-with, bp-unused-extra-name, path-parent-one, path-parent-two-nested-with | |
| decorators.inline | built-in inline decorator | match | inline-partial-basic, inline-partial-in-each, inline-partial-scoped-to-block, inline-partial-overrides-registered, decorator-inline-no-name-throws | |
| Utils.__esModule | — | missing | — | interop artefact |
| Utils.appendContextPath | — | missing | — | only used by trackIds compilation |
| Utils.blockParams | handlebars.Options.FnWithBlockParams / Options.BlockParams | missing | bp-custom-helper | no standalone constructor; the capability exists on Options |
| Utils.createFrame | handlebars.CreateFrame | match | utils-createframe, utils-createframe-empty | |
| Utils.escapeExpression | handlebars.EscapeExpression | differs | esc-expr-* | see the top-level row |
| Utils.extend | handlebars.Extend | match | utils-extend, utils-extend-empty-sources | |
| Utils.indexOf | — | missing | — | JS shim |
| Utils.isArray | handlebars.IsArray | match | utils-isarray-array, utils-isarray-object, utils-isarray-null, utils-isarray-string | |
| Utils.isEmpty | handlebars.IsEmpty | match | utils-isempty-null, utils-isempty-false, utils-isempty-zero, utils-isempty-empty-string, utils-isempty-empty-array, utils-isempty-empty-object, utils-isempty-nonempty-array | including the "empty object is not empty" rule |
| Utils.isFunction | — | missing | — | not meaningful: the port does not call function-valued context members |
| Utils.toString | — | missing | — | handlebars.Stringify is the port's own stringifier, not this shim |
| noEscape | handlebars.NoEscape() | match | opt-noescape, opt-noescape-triple-unaffected, opt-combined-noescape-strict | |
| strict | handlebars.Strict() | match | opt-strict-missing-throws, opt-strict-missing-nested-throws, opt-strict-present-ok, opt-strict-unknown-helper-throws, opt-strict-unknown-block-helper-throws, opt-strict-null-value | both sides throw/error on the same inputs |
| knownHelpers | handlebars.KnownHelpers(...) | match | opt-knownhelpers-allows-name | |
| knownHelpersOnly | handlebars.KnownHelpersOnly() | match | opt-knownhelpersonly-treats-name-as-path, opt-knownhelpersonly-builtins-still-work, opt-knownhelpersonly-unknown-with-args | |
| data: false | handlebars.NoData() | differs | opt-nodata, opt-nodata-root | upstream 4.7.8 still exposes @index/@root; the port actually suppresses them |
| compat | — | missing | opt-compat-parent-scope-lookup, opt-compat-off-parent-scope-lookup | not implemented; the Go runner reports an error for the option |
| assumeObjects | — | missing | — | |
| preventIndent | — | missing | — | the port always indents standalone partials (partial-indentation-preserved) |
| ignoreStandalone | — | missing | — | the standalone-whitespace rule cannot be turned off |
| explicitPartialContext | — | missing | — | |
| trackIds | — | missing | — | compiler internal |
| stringParams | — | missing | — | compiler internal |
| srcName | — | missing | — | source-map input, N/A |
| destName | — | missing | — | source-map output, N/A |
| — | handlebars.Render, MustParse, Template.MustRender, ParseString | extra | most cases go through Compile/Render | Go ergonomics |
| — | handlebars.EscapeHTML | extra | — | string-in/string-out variant of escapeExpression |
| — | handlebars.Stringify | extra | exercised indirectly by every val-* case | the port's canonical value formatter |
| — | handlebars.Template.HasHelper / HasPartial / HelperNames / PartialNames / RegisterHelpers / RegisterPartials / Clone | extra | — | introspection and bulk registration |
| — | handlebars.Options (Arg, Hash, HashStr, Fn, FnWith, FnWithBlockParams, Inverse, BlockParams) | extra | helpers-custom group | Go helper calling convention; no this accessor (see Divergences) |
| — | handlebars.DecoratorOptions (Arg, RegisterPartial) | extra | decorator-* | |
| — | inline helpers eq ne not and or gt lt gte lte | extra | — | upstream has no such built-ins (upstream throws on {{eq a b}}), so they are not comparable |
Every case the harness streamed to both runners, with the exact upstream symbol and Go symbol it exercised. A deliberate, documented difference is a deviation and is counted apart from a mismatch.
| Case | Group | Upstream symbol | Go symbol | Status | Note |
|---|---|---|---|---|---|
| bp-each-item | blockparams | Handlebars.helpers.each | handlebars.Compile | match | |
| bp-each-item-index | blockparams | Handlebars.helpers.each | handlebars.Compile | match | |
| bp-each-object-key | blockparams | Handlebars.helpers.each | handlebars.Compile | match | |
| bp-with | blockparams | Handlebars.helpers.with | handlebars.Compile | match | |
| bp-shadowing | blockparams | Handlebars.helpers.each | handlebars.Compile | match | |
| bp-outer-visible-inside | blockparams | Handlebars.helpers.each | handlebars.Compile | match | |
| bp-context-still-available | blockparams | Handlebars.helpers.each | handlebars.Compile | match | |
| bp-custom-helper | blockparams | options.fn(this,{blockParams}) | handlebars.Options.FnWithBlockParams | match | |
| bp-unused-extra-name | blockparams | Handlebars.helpers.with | handlebars.Compile | match | |
| if-true | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-else | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-falsy-empty-string | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-falsy-zero | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-include-zero | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-falsy-empty-array | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-truthy-empty-object | builtins | Handlebars.Utils.isEmpty | handlebars.IsEmpty | match | an empty object is NOT empty in handlebars |
| if-missing | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-null | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-chained-else-if | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-chained-else-if-fallthrough | builtins | Handlebars.helpers.if | handlebars.Compile | match | |
| if-chained-else-unless | builtins | Handlebars.helpers.unless | handlebars.Compile | match | |
| if-chained-else-with | builtins | Handlebars.helpers.with | handlebars.Compile | match | |
| unless-basic | builtins | Handlebars.helpers.unless | handlebars.Compile | match | |
| unless-else | builtins | Handlebars.helpers.unless | handlebars.Compile | match | |
| inverse-section | builtins | Handlebars.helpers.blockHelperMissing | handlebars.Compile | match | |
| inverse-section-truthy | builtins | Handlebars.helpers.blockHelperMissing | handlebars.Compile | match | |
| mustache-section-object | builtins | Handlebars.helpers.blockHelperMissing | handlebars.Compile | match | |
| mustache-section-array | builtins | Handlebars.helpers.blockHelperMissing | handlebars.Compile | match | |
| mustache-section-empty-array | builtins | Handlebars.helpers.blockHelperMissing | handlebars.Compile | match | |
| each-array | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-data-vars | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-object-key | builtins | Handlebars.helpers.each | handlebars.Compile | match | keys given in sorted order; Go sorts map keys, JS keeps insertion order |
| each-object-insertion-order | builtins | Handlebars.helpers.each | handlebars.Compile | mismatch | JS iterates insertion order (z,a,m); Go maps are unordered so the port sorts |
| each-object-index | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-empty-else | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-missing-else | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-null | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-scalar | builtins | Handlebars.helpers.each | handlebars.Compile | match | each over a non-collection |
| each-nested-objects | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| each-nested-parent-index | builtins | Handlebars.helpers.each | handlebars.Compile | match | |
| with-object | builtins | Handlebars.helpers.with | handlebars.Compile | match | |
| with-falsy-else | builtins | Handlebars.helpers.with | handlebars.Compile | match | |
| with-missing | builtins | Handlebars.helpers.with | handlebars.Compile | match | |
| with-scalar | builtins | Handlebars.helpers.with | handlebars.Compile | match | |
| lookup-by-key | builtins | Handlebars.helpers.lookup | handlebars.Compile | match | |
| lookup-array-index | builtins | Handlebars.helpers.lookup | handlebars.Compile | match | |
| lookup-missing | builtins | Handlebars.helpers.lookup | handlebars.Compile | match | |
| lookup-null-target | builtins | Handlebars.helpers.lookup | handlebars.Compile | match | |
| log-helper | builtins | Handlebars.helpers.log | handlebars.Compile | match | log writes to the logger, never to the output or stdout |
| log-helper-with-level | builtins | Handlebars.helpers.log | handlebars.Compile | match | |
| block-unclosed-throws | builtins | Handlebars.compile | handlebars.Compile | match | both sides must fail |
| block-mismatched-close-throws | builtins | Handlebars.compile | handlebars.Compile | mismatch | both sides must fail |
| helper-inline-escaped-result | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | a helper's plain string result is HTML-escaped |
| helper-inline-safestring | helpers-custom | Handlebars.SafeString | handlebars.SafeString | match | |
| helper-inline-safestring-triple | helpers-custom | Handlebars.SafeString | handlebars.SafeString | match | |
| helper-numeric-result | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | |
| helper-hash-args | helpers-custom | options.hash | handlebars.Options.Hash | match | |
| helper-hash-default | helpers-custom | options.hash | handlebars.Options.HashStr | match | |
| helper-no-args | helpers-custom | options.hash | handlebars.Options.Hash | match | |
| helper-block-literal-body | helpers-custom | options.fn | handlebars.Options.Fn | match | |
| helper-block-body-uses-context | helpers-custom | options.fn(this) | handlebars.Options.Fn | mismatch | JS passes `this`; the Go Options type exposes no accessor for the current context |
| helper-block-inverse-taken | helpers-custom | options.inverse | handlebars.Options.Inverse | match | |
| helper-block-main-taken | helpers-custom | options.fn | handlebars.Options.Fn | match | |
| helper-block-no-inverse | helpers-custom | options.inverse | handlebars.Options.Inverse | match | |
| helper-inverted-block-form | helpers-custom | options.inverse | handlebars.Options.Inverse | match | {{^name}} swaps program and inverse |
| helper-shadows-context-key | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | with arguments the helper wins over the context key |
| helper-bare-name-prefers-helper | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | ambiguous bare name: the registered helper wins over the context key |
| helper-missing-hook-bare | helpers-custom | Handlebars.helpers.helperMissing | handlebars.Template.RegisterHelper | match | |
| helper-missing-hook-with-args | helpers-custom | Handlebars.helpers.helperMissing | handlebars.Template.RegisterHelper | match | |
| block-helper-missing-hook | helpers-custom | Handlebars.helpers.blockHelperMissing | handlebars.Template.RegisterHelper | match | |
| helper-in-subexpression-of-each | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | |
| helper-with-data-var-arg | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | |
| helper-with-parent-path-arg | helpers-custom | Handlebars.registerHelper | handlebars.Template.RegisterHelper | match | |
| var-basic | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-escape-html | interpolation | Handlebars.Utils.escapeExpression | handlebars.EscapeHTML | match | escaping set is & < > " ' ` = |
| var-triple-stash | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-amp-unescaped | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-missing-bare | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-missing-dotted | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-missing-through-null | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-this | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-dot | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-this-field | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-literal-string-ref | interpolation | Handlebars.compile | handlebars.Compile | match | string literal in leading position is a literal reference |
| var-literal-number-ref | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-bracketed-segment | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-empty-template | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-no-mustaches | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-repeated | interpolation | Handlebars.compile | handlebars.Compile | match | |
| var-already-escaped-input | interpolation | Handlebars.compile | handlebars.Compile | match | double escaping is expected on both sides |
| var-unicode | interpolation | Handlebars.compile | handlebars.Compile | match | |
| comment-inline | interpolation | Handlebars.compile | handlebars.Compile | match | |
| comment-long-form | interpolation | Handlebars.compile | handlebars.Compile | match | |
| comment-standalone | interpolation | Handlebars.compile | handlebars.Compile | match | standalone comment line is removed entirely |
| opt-noescape | options | compile({noEscape:true}) | handlebars.NoEscape | match | |
| opt-noescape-triple-unaffected | options | compile({noEscape:true}) | handlebars.NoEscape | match | |
| opt-strict-missing-throws | options | compile({strict:true}) | handlebars.Strict | match | both sides must fail |
| opt-strict-missing-nested-throws | options | compile({strict:true}) | handlebars.Strict | match | both sides must fail |
| opt-strict-present-ok | options | compile({strict:true}) | handlebars.Strict | match | |
| opt-strict-unknown-helper-throws | options | compile({strict:true}) | handlebars.Strict | match | both sides must fail: unknown helper in strict mode |