From 680bcb4abb59ffbf848b30dd5740bee2a6c25550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Luba=C5=84ski?= Date: Thu, 25 Jun 2020 16:48:25 +0200 Subject: [PATCH] fix(html): print pretty logs in dev environment for expressions --- src/template/core.js | 59 +++++++++++++++++++++++++++++++++++++++++++- test/spec/html.js | 39 ++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/template/core.js b/src/template/core.js index bda28aeb..67a60cd8 100644 --- a/src/template/core.js +++ b/src/template/core.js @@ -164,6 +164,45 @@ const createWalker = const container = document.createElement("div"); const styleSheetsMap = new Map(); +function normalizeWhitespace(input, startIndent = 0) { + input = input.replace(/(^[\n\s\t ]+)|([\n\s\t ]+$)+/g, ""); + + let i = input.indexOf("\n"); + if (i > -1) { + let indent = 0 - startIndent - 2; + for (i += 1; input[i] === " " && i < input.length; i += 1) { + indent += 1; + } + return input.replace(/\n +/g, t => + t.substr(0, Math.max(t.length - indent, 1)), + ); + } + + return input; +} + +function beautifyTemplateLog(input, index) { + const placeholder = getPlaceholder(index); + + const output = normalizeWhitespace(input) + .split("\n") + .filter(i => i) + .map(line => { + const startIndex = line.indexOf(placeholder); + + if (startIndex > -1) { + return `| ${line}\n--${"-".repeat(startIndex)}${"^".repeat(6)}`; + } + + return `| ${line}`; + }) + .join("\n") + // eslint-disable-next-line no-template-curly-in-string + .replace(PLACEHOLDER_REGEXP_ALL, "${...}"); + + return `${output}`; +} + export function compileTemplate(rawParts, isSVG, styles) { const template = document.createElement("template"); const parts = []; @@ -401,7 +440,25 @@ export function compileTemplate(rawParts, isSVG, styles) { for (let index = 0; index < data.markers.length; index += 1) { const [node, marker] = data.markers[index]; if (!prevArgs || prevArgs[index] !== args[index]) { - marker(host, node, args[index], prevArgs ? prevArgs[index] : undefined); + try { + marker( + host, + node, + args[index], + prevArgs ? prevArgs[index] : undefined, + ); + } catch (error) { + if (process.env.NODE_ENV !== "production") { + // eslint-disable-next-line no-console + console.error( + `An error was thrown when updating a template expression:\n${beautifyTemplateLog( + signature, + index, + )}`, + ); + } + throw error; + } } } diff --git a/test/spec/html.js b/test/spec/html.js index 391fd529..31fc6c26 100644 --- a/test/spec/html.js +++ b/test/spec/html.js @@ -4,6 +4,7 @@ import define from "../../src/define.js"; import renderFactory from "../../src/render.js"; import { dispatch, IS_IE } from "../../src/utils.js"; import { test, resolveTimeout } from "../helpers.js"; +import { property } from "../../src/index.js"; describe("html:", () => { let fragment; @@ -61,7 +62,6 @@ describe("html:", () => { }); it("throws for missing custom element in dev environment", () => { - window.env = "development"; expect(() => html` @@ -76,6 +76,7 @@ describe("html:", () => { `(fragment), ).not.toThrow(); + window.env = "development"; }); it("clears arguments cache when template changes", () => { @@ -115,6 +116,42 @@ describe("html:", () => { }); }); + it("logs template with highlight expression when an error occur", () => { + define("test-html-object-property", { + model: property({}), + }); + + const renderWithNewLines = value => html` + +
+
+ `; + + // eslint-disable-next-line prettier/prettier + const renderWithoutNewLines = value => html``; + + spyOn(console, "error"); + + expect(() => { + renderWithNewLines(undefined)(fragment); + }).toThrow(); + expect(() => { + renderWithoutNewLines(undefined)(fragment); + }).toThrow(); + + expect(console.error).toHaveBeenCalledTimes(2); + + window.env = "production"; + + expect(() => { + renderWithoutNewLines(true)(fragment); + }).toThrow(); + + expect(console.error).toHaveBeenCalledTimes(2); + + window.env = "development"; + }); + describe("attribute expression with combined text value", () => { const render = (two, three) => html`