From 4a528eff3ad48bcc05fe2ace9e2ded8d959012eb Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 17 Oct 2025 20:17:33 -0500 Subject: [PATCH 01/10] Convert to ESM --- .eslintrc.js | 17 ---------------- demo/eleventy-config.js | 4 ++-- package.json | 8 ++++---- src/HighlightLines.js | 4 +--- src/HighlightLinesGroup.js | 6 ++---- src/HighlightPairedShortcode.js | 10 +++++----- src/LiquidHighlightTag.js | 6 ++---- src/PrismLoader.js | 13 ++++++------ src/PrismNormalizeAlias.js | 6 +++++- src/getAttributes.js | 4 +--- src/hasTemplateFormat.js | 2 +- src/markdownSyntaxHighlightOptions.js | 10 +++++----- .eleventy.js => syntaxHighlight.js | 20 +++++++++---------- test/11tyjs-diff/.eleventy.js | 5 ----- test/11tyjs-diff/eleventy.config.js | 5 +++++ .../{test.11ty.js => test.11ty.cjs} | 0 test/11tyjs-test/.eleventy.js | 5 ----- test/11tyjs-test/eleventy.config.js | 5 +++++ .../{test.11ty.js => test.11ty.cjs} | 0 test/{EleventyTest.mjs => EleventyTest.js} | 4 ++-- test/GetAttributesTest.js | 4 ++-- test/HasTemplateFormatTest.js | 4 ++-- test/HighlightLinesGroupTest.js | 4 ++-- test/HighlightLinesTest.js | 4 ++-- test/HighlightPairedShortcodeTest.js | 4 ++-- ...tionTest.mjs => JavaScriptFunctionTest.js} | 4 ++-- test/LiquidHighlightTagTest.js | 6 +++--- test/MarkdownHighlightTest.js | 6 +++--- test/issue-75/.eleventy.js | 5 ----- test/issue-75/eleventy.config.js | 5 +++++ test/issue-80/.eleventy.js | 5 ----- test/issue-80/eleventy.config.js | 5 +++++ 32 files changed, 85 insertions(+), 105 deletions(-) delete mode 100644 .eslintrc.js rename .eleventy.js => syntaxHighlight.js (76%) delete mode 100644 test/11tyjs-diff/.eleventy.js create mode 100644 test/11tyjs-diff/eleventy.config.js rename test/11tyjs-diff/{test.11ty.js => test.11ty.cjs} (100%) delete mode 100644 test/11tyjs-test/.eleventy.js create mode 100644 test/11tyjs-test/eleventy.config.js rename test/11tyjs-test/{test.11ty.js => test.11ty.cjs} (100%) rename test/{EleventyTest.mjs => EleventyTest.js} (94%) rename test/{JavaScriptFunctionTest.mjs => JavaScriptFunctionTest.js} (91%) delete mode 100644 test/issue-75/.eleventy.js create mode 100644 test/issue-75/eleventy.config.js delete mode 100644 test/issue-80/.eleventy.js create mode 100644 test/issue-80/eleventy.config.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 62e8c16..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - env: { - es6: true, - node: true - }, - extends: "eslint:recommended", - parserOptions: { - sourceType: "module", - ecmaVersion: 2017 - }, - rules: { - indent: ["error", 2], - "linebreak-style": ["error", "unix"], - quotes: ["error", "double"], - semi: ["error", "always"] - } -}; diff --git a/demo/eleventy-config.js b/demo/eleventy-config.js index c6f37fc..89a6295 100644 --- a/demo/eleventy-config.js +++ b/demo/eleventy-config.js @@ -1,6 +1,6 @@ -const syntaxHighlight = require("../.eleventy.js"); +import syntaxHighlight from "../syntaxHighlight.js"; -module.exports = function(eleventyConfig) { +export default function(eleventyConfig) { eleventyConfig.addPlugin(syntaxHighlight, { // alwaysWrapLineHighlights: true preAttributes: { tabindex: 0 } diff --git a/package.json b/package.json index e1d622c..1898dca 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,9 @@ "publishConfig": { "access": "public" }, - "main": ".eleventy.js", + "main": "syntaxHighlight.js", "scripts": { "test": "npx ava", - "demo": "npx @11ty/eleventy --input=demo --output=demo/_site --config=demo/eleventy-config.js", "start": "npx @11ty/eleventy --input=demo --output=demo/_site --config=demo/eleventy-config.js --serve" }, "repository": { @@ -49,7 +48,8 @@ "failFast": false, "files": [ "./test/*.js", - "./test/*.mjs" + "./test/*.cjs" ] - } + }, + "type": "module" } diff --git a/src/HighlightLines.js b/src/HighlightLines.js index 4aa8312..16609d8 100644 --- a/src/HighlightLines.js +++ b/src/HighlightLines.js @@ -1,4 +1,4 @@ -class HighlightLines { +export default class HighlightLines { constructor(rangeStr) { this.highlights = this.convertRangeToHash(rangeStr); } @@ -29,5 +29,3 @@ class HighlightLines { return !!this.highlights[lineNumber]; } } - -module.exports = HighlightLines; diff --git a/src/HighlightLinesGroup.js b/src/HighlightLinesGroup.js index 47c0aaa..431bdb9 100644 --- a/src/HighlightLinesGroup.js +++ b/src/HighlightLinesGroup.js @@ -1,6 +1,6 @@ -const HighlightLines = require("./HighlightLines"); +import HighlightLines from "./HighlightLines.js"; -class HighlightLinesGroup { +export default class HighlightLinesGroup { constructor(str, delimiter) { this.init(str, delimiter); } @@ -64,5 +64,3 @@ class HighlightLinesGroup { return this.splitLineMarkup( line, ``, ``); } } - -module.exports = HighlightLinesGroup; diff --git a/src/HighlightPairedShortcode.js b/src/HighlightPairedShortcode.js index be37d52..0b91aa5 100644 --- a/src/HighlightPairedShortcode.js +++ b/src/HighlightPairedShortcode.js @@ -1,9 +1,9 @@ -const Prism = require("prismjs"); -const PrismLoader = require("./PrismLoader"); -const HighlightLinesGroup = require("./HighlightLinesGroup"); -const getAttributes = require("./getAttributes"); +import Prism from "prismjs"; +import PrismLoader from "./PrismLoader.js"; +import HighlightLinesGroup from "./HighlightLinesGroup.js"; +import getAttributes from "./getAttributes.js"; -module.exports = function (content, language, highlightNumbers, options = {}) { +export default function (content, language, highlightNumbers, options = {}) { // default to on if(options.trim === undefined || options.trim === true) { content = content.trim(); diff --git a/src/LiquidHighlightTag.js b/src/LiquidHighlightTag.js index 26eb7ce..6626f87 100644 --- a/src/LiquidHighlightTag.js +++ b/src/LiquidHighlightTag.js @@ -1,6 +1,6 @@ -const HighlightPairedShortcode = require("./HighlightPairedShortcode"); +import HighlightPairedShortcode from "./HighlightPairedShortcode.js"; -class LiquidHighlightTag { +export class LiquidHighlightTag { constructor(liquidEngine) { this.liquidEngine = liquidEngine; } @@ -45,5 +45,3 @@ class LiquidHighlightTag { return ret(this); } } - -module.exports = LiquidHighlightTag; diff --git a/src/PrismLoader.js b/src/PrismLoader.js index 5fa6007..ab9e325 100644 --- a/src/PrismLoader.js +++ b/src/PrismLoader.js @@ -1,16 +1,17 @@ -const Prism = require("prismjs"); -const PrismLoader = require("prismjs/components/index.js"); +import Prism from "prismjs"; +import PrismLoader from "prismjs/components/index.js"; + // Avoid "Language does not exist: " console logs PrismLoader.silent = true; -require("prismjs/components/prism-diff.js"); +import "prismjs/components/prism-diff.js"; // Load diff-highlight plugin -require("prismjs/plugins/diff-highlight/prism-diff-highlight"); +import "prismjs/plugins/diff-highlight/prism-diff-highlight.js"; -const PrismAlias = require("./PrismNormalizeAlias"); +import PrismAlias from "./PrismNormalizeAlias.js"; -module.exports = function(language, options = {}) { +export default function(language, options = {}) { let diffRemovedRawName = language; if(language.startsWith("diff-")) { diffRemovedRawName = language.substr("diff-".length); diff --git a/src/PrismNormalizeAlias.js b/src/PrismNormalizeAlias.js index 7738e05..c593c24 100644 --- a/src/PrismNormalizeAlias.js +++ b/src/PrismNormalizeAlias.js @@ -3,9 +3,13 @@ const HARDCODED_ALIASES = { nunjucks: "jinja2", }; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); + // This was added to make `ts` resolve to `typescript` correctly. // The Prism loader doesn’t seem to always handle aliasing correctly. -module.exports = function(language) { +export default function(language) { try { // Careful this is not public API stuff: // https://github.com/PrismJS/prism/issues/2146 diff --git a/src/getAttributes.js b/src/getAttributes.js index 603c6a5..4155255 100644 --- a/src/getAttributes.js +++ b/src/getAttributes.js @@ -34,7 +34,7 @@ function attributeEntryToString(attribute, context) { * @param {object} context.options The options passed to the syntax highlighter. * @returns {string} A string containing the above HTML attributes preceded by a single space. */ -function getAttributes(attributes, context = {}) { +export default function getAttributes(attributes, context = {}) { let langClass = context.language ? `language-${context.language}` : ""; if (!attributes) { @@ -58,5 +58,3 @@ function getAttributes(attributes, context = {}) { throw new Error("Syntax highlighter plugin custom attributes on
 and  must be an object. Received: " + JSON.stringify(attributes));
   }
 }
-
-module.exports = getAttributes;
diff --git a/src/hasTemplateFormat.js b/src/hasTemplateFormat.js
index ee81e89..10c0b64 100644
--- a/src/hasTemplateFormat.js
+++ b/src/hasTemplateFormat.js
@@ -1,4 +1,4 @@
-module.exports = function(templateFormats = ["*"], format = false) {
+export default function(templateFormats = ["*"], format = false) {
   if(!Array.isArray(templateFormats)) {
     templateFormats = [templateFormats];
   }
diff --git a/src/markdownSyntaxHighlightOptions.js b/src/markdownSyntaxHighlightOptions.js
index 098c683..337d4eb 100644
--- a/src/markdownSyntaxHighlightOptions.js
+++ b/src/markdownSyntaxHighlightOptions.js
@@ -1,9 +1,9 @@
-const Prism = require("prismjs");
-const PrismLoader = require("./PrismLoader");
-const HighlightLinesGroup = require("./HighlightLinesGroup");
-const getAttributes = require("./getAttributes");
+import Prism from "prismjs";
+import PrismLoader from "./PrismLoader.js";
+import HighlightLinesGroup from "./HighlightLinesGroup.js";
+import getAttributes from "./getAttributes.js";
 
-module.exports = function (options = {}) {
+export default function (options = {}) {
   return function(str, language) {
     if(!language) {
       // empty string means defer to the upstream escaping code built into markdown lib.
diff --git a/.eleventy.js b/syntaxHighlight.js
similarity index 76%
rename from .eleventy.js
rename to syntaxHighlight.js
index a3b1838..6c7c7df 100644
--- a/.eleventy.js
+++ b/syntaxHighlight.js
@@ -1,12 +1,12 @@
-const pkg = require("./package.json");
-const Prism = require("prismjs");
-const PrismLoader = require("./src/PrismLoader");
-const hasTemplateFormat = require("./src/hasTemplateFormat");
-const HighlightPairedShortcode = require("./src/HighlightPairedShortcode");
-const LiquidHighlightTag = require("./src/LiquidHighlightTag");
-const markdownPrismJs = require("./src/markdownSyntaxHighlightOptions");
-
-module.exports = function(eleventyConfig, options){
+import pkg from "./package.json" with { type: "json" };
+import Prism from "prismjs";
+import PrismLoader from "./src/PrismLoader.js";
+import hasTemplateFormat from "./src/hasTemplateFormat.js";
+import HighlightPairedShortcode from "./src/HighlightPairedShortcode.js";
+import { LiquidHighlightTag } from "./src/LiquidHighlightTag.js";
+import markdownPrismJs from "./src/markdownSyntaxHighlightOptions.js";
+
+export default function(eleventyConfig, options){
   try {
     eleventyConfig.versionCheck(pkg["11ty"].compatibility);
   } catch(e) {
@@ -57,4 +57,4 @@ module.exports = function(eleventyConfig, options){
   options.init({Prism})
 };
 
-module.exports.pairedShortcode = HighlightPairedShortcode;
+export { HighlightPairedShortcode as pairedShortcode };
diff --git a/test/11tyjs-diff/.eleventy.js b/test/11tyjs-diff/.eleventy.js
deleted file mode 100644
index 40a3b76..0000000
--- a/test/11tyjs-diff/.eleventy.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const syntaxHighlight = require("../../");
-
-module.exports = function(eleventyConfig) {
-  eleventyConfig.addPlugin(syntaxHighlight);
-};
diff --git a/test/11tyjs-diff/eleventy.config.js b/test/11tyjs-diff/eleventy.config.js
new file mode 100644
index 0000000..f04b28c
--- /dev/null
+++ b/test/11tyjs-diff/eleventy.config.js
@@ -0,0 +1,5 @@
+import syntaxHighlight from "../../syntaxHighlight.js";
+
+export default function(eleventyConfig) {
+  eleventyConfig.addPlugin(syntaxHighlight);
+};
diff --git a/test/11tyjs-diff/test.11ty.js b/test/11tyjs-diff/test.11ty.cjs
similarity index 100%
rename from test/11tyjs-diff/test.11ty.js
rename to test/11tyjs-diff/test.11ty.cjs
diff --git a/test/11tyjs-test/.eleventy.js b/test/11tyjs-test/.eleventy.js
deleted file mode 100644
index 40a3b76..0000000
--- a/test/11tyjs-test/.eleventy.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const syntaxHighlight = require("../../");
-
-module.exports = function(eleventyConfig) {
-  eleventyConfig.addPlugin(syntaxHighlight);
-};
diff --git a/test/11tyjs-test/eleventy.config.js b/test/11tyjs-test/eleventy.config.js
new file mode 100644
index 0000000..f04b28c
--- /dev/null
+++ b/test/11tyjs-test/eleventy.config.js
@@ -0,0 +1,5 @@
+import syntaxHighlight from "../../syntaxHighlight.js";
+
+export default function(eleventyConfig) {
+  eleventyConfig.addPlugin(syntaxHighlight);
+};
diff --git a/test/11tyjs-test/test.11ty.js b/test/11tyjs-test/test.11ty.cjs
similarity index 100%
rename from test/11tyjs-test/test.11ty.js
rename to test/11tyjs-test/test.11ty.cjs
diff --git a/test/EleventyTest.mjs b/test/EleventyTest.js
similarity index 94%
rename from test/EleventyTest.mjs
rename to test/EleventyTest.js
index 21aaeaf..9b5211a 100644
--- a/test/EleventyTest.mjs
+++ b/test/EleventyTest.js
@@ -7,7 +7,7 @@ function normalizeNewLines(str) {
 
 test("Diff output escaped #75", async t => {
   let elev = new Eleventy("./test/issue-75/", "./test/issue-75/_site/", {
-    configPath: "./test/issue-75/.eleventy.js"
+    configPath: "./test/issue-75/eleventy.config.js"
   });
   let json = await elev.toJSON();
 
@@ -18,7 +18,7 @@ test("Diff output escaped #75", async t => {
 
 test("diff-javascript #80", async t => {
   let elev = new Eleventy("./test/issue-80/", "./test/issue-80/_site/", {
-    configPath: "./test/issue-80/.eleventy.js"
+    configPath: "./test/issue-80/eleventy.config.js"
   });
   let json = await elev.toJSON();
 
diff --git a/test/GetAttributesTest.js b/test/GetAttributesTest.js
index d555968..2d3848e 100644
--- a/test/GetAttributesTest.js
+++ b/test/GetAttributesTest.js
@@ -1,5 +1,5 @@
-const test = require("ava");
-const ga = require("../src/getAttributes");
+import test from "ava";
+import ga from "../src/getAttributes.js";
 
 test("Falsy", t => {
   t.is(ga(false), "");
diff --git a/test/HasTemplateFormatTest.js b/test/HasTemplateFormatTest.js
index ad0cda5..71ac511 100644
--- a/test/HasTemplateFormatTest.js
+++ b/test/HasTemplateFormatTest.js
@@ -1,5 +1,5 @@
-const test = require("ava");
-const hasTemplateFormat = require("../src/hasTemplateFormat");
+import test from "ava";
+import hasTemplateFormat from "../src/hasTemplateFormat.js";
 
 test("hasTemplateFormats", t => {
   t.true(hasTemplateFormat("*", "liquid"));
diff --git a/test/HighlightLinesGroupTest.js b/test/HighlightLinesGroupTest.js
index 9c575a8..f413112 100644
--- a/test/HighlightLinesGroupTest.js
+++ b/test/HighlightLinesGroupTest.js
@@ -1,5 +1,5 @@
-const test = require("ava");
-const HighlightLinesGroup = require("../src/HighlightLinesGroup");
+import test from "ava";
+import HighlightLinesGroup from "../src/HighlightLinesGroup.js";
 
 test("Empty", t => {
   let hilite = new HighlightLinesGroup("");
diff --git a/test/HighlightLinesTest.js b/test/HighlightLinesTest.js
index cbbb330..4042af3 100644
--- a/test/HighlightLinesTest.js
+++ b/test/HighlightLinesTest.js
@@ -1,5 +1,5 @@
-const test = require("ava");
-const HighlightLines = require("../src/HighlightLines");
+import test from "ava";
+import HighlightLines from "../src/HighlightLines.js";
 
 test("HighlightLines empty", t => {
   let hilite = new HighlightLines("");
diff --git a/test/HighlightPairedShortcodeTest.js b/test/HighlightPairedShortcodeTest.js
index d4217a5..3ba9559 100644
--- a/test/HighlightPairedShortcodeTest.js
+++ b/test/HighlightPairedShortcodeTest.js
@@ -1,5 +1,5 @@
-const test = require("ava");
-const HighlightPairedShortcode = require("../src/HighlightPairedShortcode");
+import test from "ava";
+import HighlightPairedShortcode from "../src/HighlightPairedShortcode.js";
 
 test("Base", async t => {
   t.is(await HighlightPairedShortcode(`alert();
diff --git a/test/JavaScriptFunctionTest.mjs b/test/JavaScriptFunctionTest.js
similarity index 91%
rename from test/JavaScriptFunctionTest.mjs
rename to test/JavaScriptFunctionTest.js
index 22d8aba..8b241a7 100644
--- a/test/JavaScriptFunctionTest.mjs
+++ b/test/JavaScriptFunctionTest.js
@@ -3,7 +3,7 @@ import Eleventy from '@11ty/eleventy';
 
 test("JavaScript Function", async t => {
   let elev = new Eleventy("./test/11tyjs-test/", "./test/11tyjs-test/_site/", {
-    configPath: "./test/11tyjs-test/.eleventy.js"
+    configPath: "./test/11tyjs-test/eleventy.config.js"
   });
   let json = await elev.toJSON();
 
@@ -14,7 +14,7 @@ test("JavaScript Function", async t => {
 
 test("JavaScript Function Diff #76", async t => {
   let elev = new Eleventy("./test/11tyjs-diff/", "./test/11tyjs-diff/_site/", {
-    configPath: "./test/11tyjs-diff/.eleventy.js"
+    configPath: "./test/11tyjs-diff/eleventy.config.js"
   });
   let json = await elev.toJSON();
 
diff --git a/test/LiquidHighlightTagTest.js b/test/LiquidHighlightTagTest.js
index 8d6eaa4..946c6d0 100644
--- a/test/LiquidHighlightTagTest.js
+++ b/test/LiquidHighlightTagTest.js
@@ -1,6 +1,6 @@
-const test = require("ava");
-const { Liquid } = require('liquidjs');
-const LiquidHighlightTag = require("../src/LiquidHighlightTag");
+import test from "ava";
+import { Liquid } from 'liquidjs';
+import { LiquidHighlightTag } from "../src/LiquidHighlightTag.js";
 
 async function renderLiquid(str, data = {}, engine = null) {
 	if(!engine) {
diff --git a/test/MarkdownHighlightTest.js b/test/MarkdownHighlightTest.js
index e7a7cea..abbc2e0 100644
--- a/test/MarkdownHighlightTest.js
+++ b/test/MarkdownHighlightTest.js
@@ -1,6 +1,6 @@
-const test = require("ava");
-const md = require("markdown-it");
-const markdownPrismJsOptions = require("../src/markdownSyntaxHighlightOptions");
+import test from "ava";
+import md from "markdown-it";
+import markdownPrismJsOptions from "../src/markdownSyntaxHighlightOptions.js";
 
 test("Test Markdown Highlighter", t => {
   let mdLib = md();
diff --git a/test/issue-75/.eleventy.js b/test/issue-75/.eleventy.js
deleted file mode 100644
index 40a3b76..0000000
--- a/test/issue-75/.eleventy.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const syntaxHighlight = require("../../");
-
-module.exports = function(eleventyConfig) {
-  eleventyConfig.addPlugin(syntaxHighlight);
-};
diff --git a/test/issue-75/eleventy.config.js b/test/issue-75/eleventy.config.js
new file mode 100644
index 0000000..f04b28c
--- /dev/null
+++ b/test/issue-75/eleventy.config.js
@@ -0,0 +1,5 @@
+import syntaxHighlight from "../../syntaxHighlight.js";
+
+export default function(eleventyConfig) {
+  eleventyConfig.addPlugin(syntaxHighlight);
+};
diff --git a/test/issue-80/.eleventy.js b/test/issue-80/.eleventy.js
deleted file mode 100644
index 40a3b76..0000000
--- a/test/issue-80/.eleventy.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const syntaxHighlight = require("../../");
-
-module.exports = function(eleventyConfig) {
-  eleventyConfig.addPlugin(syntaxHighlight);
-};
diff --git a/test/issue-80/eleventy.config.js b/test/issue-80/eleventy.config.js
new file mode 100644
index 0000000..f04b28c
--- /dev/null
+++ b/test/issue-80/eleventy.config.js
@@ -0,0 +1,5 @@
+import syntaxHighlight from "../../syntaxHighlight.js";
+
+export default function(eleventyConfig) {
+  eleventyConfig.addPlugin(syntaxHighlight);
+};

From c8203ee7c1d2481c92d611ce570af0183e9ff92a Mon Sep 17 00:00:00 2001
From: Zach Leatherman 
Date: Fri, 17 Oct 2025 20:35:25 -0500
Subject: [PATCH 02/10] =?UTF-8?q?Don=E2=80=99t=20transform=20via=20line=20?=
 =?UTF-8?q?separators=20at=20all=20if=20line=20highlights=20not=20in=20pla?=
 =?UTF-8?q?y=20(breaking)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/HighlightPairedShortcode.js       | 21 +++++++++++----------
 src/markdownSyntaxHighlightOptions.js | 12 +++++-------
 test/HighlightPairedShortcodeTest.js  | 18 +++++++++---------
 3 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/src/HighlightPairedShortcode.js b/src/HighlightPairedShortcode.js
index 0b91aa5..ff11f74 100644
--- a/src/HighlightPairedShortcode.js
+++ b/src/HighlightPairedShortcode.js
@@ -21,19 +21,20 @@ export default function (content, language, highlightNumbers, options = {}) {
     }
   }
 
-  let group = new HighlightLinesGroup(highlightNumbers);
-  let lines = highlightedContent.split(/\r?\n/);
-  lines = lines.map(function(line, j) {
-    if(options.alwaysWrapLineHighlights || highlightNumbers) {
-      let lineContent = group.getLineMarkup(j, line);
-      return lineContent;
-    }
-    return line;
-  });
+  let transformedCode = highlightedContent;
+  if(options.alwaysWrapLineHighlights || highlightNumbers) {
+    let group = new HighlightLinesGroup(highlightNumbers);
+    let lines = highlightedContent.split(/\r?\n/);
+    lines = lines.map(function(line, j) {
+      return group.getLineMarkup(j, line);
+    });
+    // default separator is "\n" upstream
+    transformedCode = lines.join(options.lineSeparator || "
"); + } const context = { content: content, language: language, options: options }; const preAttributes = getAttributes(options.preAttributes, context); const codeAttributes = getAttributes(options.codeAttributes, context); - return `` + lines.join(options.lineSeparator || "
") + "
"; + return `${transformedCode}`; }; diff --git a/src/markdownSyntaxHighlightOptions.js b/src/markdownSyntaxHighlightOptions.js index 337d4eb..e72173d 100644 --- a/src/markdownSyntaxHighlightOptions.js +++ b/src/markdownSyntaxHighlightOptions.js @@ -37,13 +37,11 @@ export default function (options = {}) { lines = lines.slice(0, -1); } - lines = lines.map(function(line, j) { - if(options.alwaysWrapLineHighlights || hasHighlightNumbers) { - let lineContent = highlights.getLineMarkup(j, line); - return lineContent; - } - return line; - }); + if(options.alwaysWrapLineHighlights || hasHighlightNumbers) { + lines = lines.map(function(line, j) { + return highlights.getLineMarkup(j, line); + }); + } const context = { content: str, language: language, options: options }; const preAttributes = getAttributes(options.preAttributes, context); diff --git a/test/HighlightPairedShortcodeTest.js b/test/HighlightPairedShortcodeTest.js index 3ba9559..960a111 100644 --- a/test/HighlightPairedShortcodeTest.js +++ b/test/HighlightPairedShortcodeTest.js @@ -13,7 +13,7 @@ test("Base with LF EOL, always wrap highlights", async t => { test("Base with LF EOL, no wrap highlights", async t => { t.is(await HighlightPairedShortcode('alert();\nalert();', -"js", "", { alwaysWrapLineHighlights: false }), `
alert();
alert();
`); +"js", "", { alwaysWrapLineHighlights: false }), `
alert();\nalert();
`); }); test("Base with CRLF EOL, always wrap highlights", async t => { @@ -23,7 +23,7 @@ test("Base with CRLF EOL, always wrap highlights", async t => { test("Base with CRLF EOL, no wrap highlights", async t => { t.is(await HighlightPairedShortcode('alert();\r\nalert();', -"js", "", { alwaysWrapLineHighlights: false }), `
alert();
alert();
`); +"js", "", { alwaysWrapLineHighlights: false }), `
alert();\r\nalert();
`); }); test("Base with custom attributes", async t => { @@ -42,7 +42,7 @@ alert();`, "js", "", { test("Base No line highlights", async t => { t.is(await HighlightPairedShortcode(`alert(); -alert();`, "js", ""), `
alert();
alert();
`); +alert();`, "js", ""), `
alert();\nalert();
`); }); test("Highlight Active", async t => { @@ -70,7 +70,7 @@ let user = "Jane User"; document.body.textContent = greeter(user);`; - t.is(await HighlightPairedShortcode(script, "typescript"), `
function greeter(person) {
return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);
`); + t.is(await HighlightPairedShortcode(script, "typescript"), `
function greeter(person) {\n    return "Hello, " + person;\n}\n\nlet user = "Jane User";\n\ndocument.body.textContent = greeter(user);
`); }); test("Test loader ts", async t => { @@ -82,7 +82,7 @@ let user = "Jane User"; document.body.textContent = greeter(user);` - t.is(await HighlightPairedShortcode(script, "ts"), `
function greeter(person) {
return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);
`); + t.is(await HighlightPairedShortcode(script, "ts"), `
function greeter(person) {\n    return "Hello, " + person;\n}\n\nlet user = "Jane User";\n\ndocument.body.textContent = greeter(user);
`); }); test("Test loader invalid language, with errorOnInvalidLanguage option", async t => { @@ -100,17 +100,17 @@ test("Test loader invalid language (should pass)", async t => { test("Test loader invalid language with ignore", async t => { let src = `hello hello` - t.is(await HighlightPairedShortcode(src, "asldkjflksdaj"), `
hello
hello
`); + t.is(await HighlightPairedShortcode(src, "asldkjflksdaj"), `
hello\nhello
`); }); test("Trim content option (defaults true)", async t => { t.is(await HighlightPairedShortcode(` alert(); -alert(); `, "js", "", {}), `
alert();
alert();
`); +alert(); `, "js", "", {}), `
alert();\nalert();
`); t.is(await HighlightPairedShortcode(` alert(); -alert(); `, "js", "", { trim: true }), `
alert();
alert();
`); +alert(); `, "js", "", { trim: true }), `
alert();\nalert();
`); t.is(await HighlightPairedShortcode(` alert(); -alert(); `, "js", "", { trim: false }), `
 alert();
alert();
`); +alert(); `, "js", "", { trim: false }), `
 alert();\nalert(); 
`); }); From 508ebfa82d039292d2cd588312d8ef6ce2f7e95f Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 17 Oct 2025 20:40:05 -0500 Subject: [PATCH 03/10] Change line separator default in shortcode function (already set upstream in default options) --- src/HighlightPairedShortcode.js | 3 +-- test/HighlightPairedShortcodeTest.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/HighlightPairedShortcode.js b/src/HighlightPairedShortcode.js index ff11f74..f3677eb 100644 --- a/src/HighlightPairedShortcode.js +++ b/src/HighlightPairedShortcode.js @@ -28,8 +28,7 @@ export default function (content, language, highlightNumbers, options = {}) { lines = lines.map(function(line, j) { return group.getLineMarkup(j, line); }); - // default separator is "\n" upstream - transformedCode = lines.join(options.lineSeparator || "
"); + transformedCode = lines.join(options.lineSeparator || "\n"); } const context = { content: content, language: language, options: options }; diff --git a/test/HighlightPairedShortcodeTest.js b/test/HighlightPairedShortcodeTest.js index 960a111..a645808 100644 --- a/test/HighlightPairedShortcodeTest.js +++ b/test/HighlightPairedShortcodeTest.js @@ -3,12 +3,12 @@ import HighlightPairedShortcode from "../src/HighlightPairedShortcode.js"; test("Base", async t => { t.is(await HighlightPairedShortcode(`alert(); -alert();`, "js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +alert();`, "js", "", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); }); test("Base with LF EOL, always wrap highlights", async t => { t.is(await HighlightPairedShortcode('alert();\nalert();', -"js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +"js", "", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); }); test("Base with LF EOL, no wrap highlights", async t => { @@ -18,7 +18,7 @@ test("Base with LF EOL, no wrap highlights", async t => { test("Base with CRLF EOL, always wrap highlights", async t => { t.is(await HighlightPairedShortcode('alert();\r\nalert();', -"js", "", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +"js", "", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); }); test("Base with CRLF EOL, no wrap highlights", async t => { @@ -28,7 +28,7 @@ test("Base with CRLF EOL, no wrap highlights", async t => { test("Base with custom attributes", async t => { t.is(await HighlightPairedShortcode(`alert(); -alert();`, "js", "", { alwaysWrapLineHighlights: true, preAttributes: { tabindex: 0, 'data-testid': 'code' } }), `
alert();
alert();
`); +alert();`, "js", "", { alwaysWrapLineHighlights: true, preAttributes: { tabindex: 0, 'data-testid': 'code' } }), `
alert();\nalert();
`); }); test("Base change the line separator", async t => { @@ -47,18 +47,18 @@ alert();`, "js", ""), `
 {
   t.is(await HighlightPairedShortcode(`alert();
-alert();`, "js", "0", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +alert();`, "js", "0", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); t.is(await HighlightPairedShortcode(`alert(); -alert();`, "js", "0-1", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +alert();`, "js", "0-1", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); }); test("Highlight Add/Remove", async t => { t.is(await HighlightPairedShortcode(`alert(); -alert();`, "js", "0 1", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +alert();`, "js", "0 1", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); t.is(await HighlightPairedShortcode(`alert(); -alert();`, "js", "1 0", { alwaysWrapLineHighlights: true }), `
alert();
alert();
`); +alert();`, "js", "1 0", { alwaysWrapLineHighlights: true }), `
alert();\nalert();
`); }); test("Test loader typescript", async t => { From af5545160af4120207fc8027a794da244c71fb94 Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 17 Oct 2025 20:44:49 -0500 Subject: [PATCH 04/10] Properly escape HTML attributes set via options --- package.json | 1 + src/getAttributes.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 1898dca..263bb56 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "markdown-it": "^14.1.0" }, "dependencies": { + "entities": "^7.0.0", "prismjs": "^1.30.0" }, "ava": { diff --git a/src/getAttributes.js b/src/getAttributes.js index 4155255..cf090a5 100644 --- a/src/getAttributes.js +++ b/src/getAttributes.js @@ -1,3 +1,5 @@ +import { escapeAttribute } from "entities/escape"; + function attributeEntryToString(attribute, context) { let [key, value] = attribute; @@ -11,6 +13,9 @@ function attributeEntryToString(attribute, context) { ); } + if(typeof value === "string") { + value = escapeAttribute(value); + } return `${key}="${value}"`; } From ec640971e2de6b07ba6d38227345ed914d9501b3 Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 17 Oct 2025 21:26:07 -0500 Subject: [PATCH 05/10] Breaking: liquid code blocks are now parsed by liquid engine (use {% raw %} to opt out) --- src/LiquidHighlightTag.js | 47 ---------------------------------- src/hasTemplateFormat.js | 2 +- syntaxHighlight.js | 30 ++++++++++------------ test/JavaScriptFunctionTest.js | 6 ++--- test/LiquidHighlightTagTest.js | 43 ------------------------------- test/LiquidTest.js | 42 ++++++++++++++++++++++++++++++ test/stubs-virtual/.gitkeep | 0 7 files changed, 60 insertions(+), 110 deletions(-) delete mode 100644 src/LiquidHighlightTag.js delete mode 100644 test/LiquidHighlightTagTest.js create mode 100644 test/LiquidTest.js create mode 100644 test/stubs-virtual/.gitkeep diff --git a/src/LiquidHighlightTag.js b/src/LiquidHighlightTag.js deleted file mode 100644 index 6626f87..0000000 --- a/src/LiquidHighlightTag.js +++ /dev/null @@ -1,47 +0,0 @@ -import HighlightPairedShortcode from "./HighlightPairedShortcode.js"; - -export class LiquidHighlightTag { - constructor(liquidEngine) { - this.liquidEngine = liquidEngine; - } - - getObject(options = {}) { - let ret = function(highlighter) { - return { - parse: function(tagToken, remainTokens) { - let split = tagToken.args.split(" "); - - this.language = split.shift(); - this.highlightLines = split.join(" "); - - this.tokens = []; - - var stream = highlighter.liquidEngine.parser.parseStream(remainTokens); - - stream - .on("token", token => { - if (token.name === "endhighlight") { - stream.stop(); - } else { - this.tokens.push(token); - } - }) - .on("end", x => { - throw new Error(`tag ${tagToken.getText()} not closed`); - }); - - stream.start(); - }, - render: function(scope, hash) { - let tokens = this.tokens.map(token => { - return token.raw || token.getText(); - }); - let tokenStr = tokens.join("").trim(); - return Promise.resolve(HighlightPairedShortcode(tokenStr, this.language, this.highlightLines, options)); - } - }; - }; - - return ret(this); - } -} diff --git a/src/hasTemplateFormat.js b/src/hasTemplateFormat.js index 10c0b64..677fb34 100644 --- a/src/hasTemplateFormat.js +++ b/src/hasTemplateFormat.js @@ -4,7 +4,7 @@ export default function(templateFormats = ["*"], format = false) { } if( Array.isArray(templateFormats) ) { - if( templateFormats.indexOf("*") > -1 || templateFormats.indexOf(format) > -1 ) { + if(templateFormats.includes("*") || format && templateFormats.includes(format)) { return true; } } diff --git a/syntaxHighlight.js b/syntaxHighlight.js index 6c7c7df..9d36fc7 100644 --- a/syntaxHighlight.js +++ b/syntaxHighlight.js @@ -3,7 +3,6 @@ import Prism from "prismjs"; import PrismLoader from "./src/PrismLoader.js"; import hasTemplateFormat from "./src/hasTemplateFormat.js"; import HighlightPairedShortcode from "./src/HighlightPairedShortcode.js"; -import { LiquidHighlightTag } from "./src/LiquidHighlightTag.js"; import markdownPrismJs from "./src/markdownSyntaxHighlightOptions.js"; export default function(eleventyConfig, options){ @@ -26,32 +25,31 @@ export default function(eleventyConfig, options){ PrismLoader(language) } + function shortcodeFunction(content, args) { + // {% highlight "js 0 2-3" %} + let [language, ...highlightNumbers] = (args || "").split(" "); + return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); + } + + if( hasTemplateFormat(options.templateFormats, "liquid") ) { - eleventyConfig.addLiquidTag("highlight", (liquidEngine) => { - // {% highlight js 0 2 %} - let highlight = new LiquidHighlightTag(liquidEngine); - return highlight.getObject(options); - }); + eleventyConfig.addPairedLiquidShortcode("highlight", shortcodeFunction); } if( hasTemplateFormat(options.templateFormats, "njk") ) { - eleventyConfig.addPairedNunjucksShortcode("highlight", (content, args) => { - // {% highlight "js 0 2-3" %} - let [language, ...highlightNumbers] = args.split(" "); - return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); - }); + eleventyConfig.addPairedNunjucksShortcode("highlight", shortcodeFunction); } if( hasTemplateFormat(options.templateFormats, "md") ) { // ```js/0,2-3 + // TODO swap to amendLibrary eleventyConfig.addMarkdownHighlighter(markdownPrismJs(options)); } - // we need to add this as many template languages (Vue, WebC) rely on JavaScript functions (not just 11ty.js) - eleventyConfig.addJavaScriptFunction("highlight", (language, content, highlight1, highlight2) => { - let highlightLines = [highlight1, highlight2].filter(entry => entry).join(" "); - let result = HighlightPairedShortcode(content, language, highlightLines, options); - return result; + // we need to add this as many template languages (WebC) rely on JavaScript functions (not just 11ty.js) + // Note the argument order for language, content (differs from shortcode) + eleventyConfig.addJavaScriptFunction("highlight", (language, content, ...highlightLines) => { + return HighlightPairedShortcode(content, language, highlightLines.join(" "), options); }); options.init({Prism}) diff --git a/test/JavaScriptFunctionTest.js b/test/JavaScriptFunctionTest.js index 8b241a7..bc9ec35 100644 --- a/test/JavaScriptFunctionTest.js +++ b/test/JavaScriptFunctionTest.js @@ -9,7 +9,7 @@ test("JavaScript Function", async t => { t.is(json.length, 1); let rendered = json[0].content; - t.is(`
var test;
`, rendered); + t.is(rendered, `
var test;
`); }); test("JavaScript Function Diff #76", async t => { @@ -20,6 +20,6 @@ test("JavaScript Function Diff #76", async t => { t.is(json.length, 1); let rendered = json[0].content; - t.is(`
-var test;
-
-var test;
`, rendered); + t.is(rendered, `
-var test;
+
-var test;
`); }); diff --git a/test/LiquidHighlightTagTest.js b/test/LiquidHighlightTagTest.js deleted file mode 100644 index 946c6d0..0000000 --- a/test/LiquidHighlightTagTest.js +++ /dev/null @@ -1,43 +0,0 @@ -import test from "ava"; -import { Liquid } from 'liquidjs'; -import { LiquidHighlightTag } from "../src/LiquidHighlightTag.js"; - -async function renderLiquid(str, data = {}, engine = null) { - if(!engine) { - engine = new Liquid(); - } - - let result = await engine.parseAndRender(str, data); - return result; -} - -test("Test Render", async t => { - t.is("Hi Zach", await renderLiquid("Hi {{name}}", {name: "Zach"})); -}); - -test("Test Highlight Tag Render", async t => { - let engine = new Liquid(); - let tag = new LiquidHighlightTag(engine); - engine.registerTag("highlight", tag.getObject()); - - let rendered = await renderLiquid("{% highlight js %}var test;{% endhighlight %}", {}, engine); - t.is(`
var test;
`, rendered); -}); - -test("Njk Alias", async t => { - let engine = new Liquid(); - let tag = new LiquidHighlightTag(engine); - engine.registerTag("highlight", tag.getObject()); - - let rendered = await renderLiquid("{% highlight njk %}{% raw %}hello{% endraw %}{% endhighlight %}", {}, engine); - t.is(`
{% raw %}hello{% endraw %}
`, rendered); -}); - -test("Nunjucks alias", async t => { - let engine = new Liquid(); - let tag = new LiquidHighlightTag(engine); - engine.registerTag("highlight", tag.getObject()); - - let rendered = await renderLiquid("{% highlight nunjucks %}{% raw %}hello{% endraw %}{% endhighlight %}", {}, engine); - t.is(`
{% raw %}hello{% endraw %}
`, rendered); -}); diff --git a/test/LiquidTest.js b/test/LiquidTest.js new file mode 100644 index 0000000..bf5b106 --- /dev/null +++ b/test/LiquidTest.js @@ -0,0 +1,42 @@ +import test from "ava"; +import Eleventy from "@11ty/eleventy"; +import syntaxHighlight from "../syntaxHighlight.js"; + +async function renderLiquid(str, data = {}) { + let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site/", { + config: function(eleventyConfig) { + eleventyConfig.addPlugin(syntaxHighlight); + eleventyConfig.addTemplate("template.liquid", str, data); + } + }); + let [result] = await elev.toJSON(); + return result?.content; +} + +test("Test Render", async t => { + t.is(await renderLiquid("Hi {{name}}", {name: "Zach"}), "Hi Zach"); +}); + +test("Test Highlight Tag Render", async t => { + let rendered = await renderLiquid("{% highlight 'js' %}var test;{% endhighlight %}"); + t.is(rendered, `
var test;
`); +}); + +test("Test for raw", async t => { + // Breaking in v6: content is first interpreted as Liquid (so make sure you use raw if you want that not to happen) + let rendered = await renderLiquid("{% highlight 'liquid' %}{% raw %}{{ 'hello' }}{% endraw %}{% endhighlight %}"); + t.is(rendered, `
{{ \'hello\' }}
`); +}); + + +test("Njk Alias", async t => { + // Breaking in v6: content is first interpreted as Liquid (so make sure you use raw if you want that not to happen) + let rendered = await renderLiquid("{% highlight 'njk' %}{% raw %}{{ 'hello' }}{% endraw %}{% endhighlight %}"); + t.is(rendered, `
{{ 'hello' }}
`); +}); + +test("Nunjucks alias", async t => { + // Breaking in v6: content is first interpreted as Liquid (so make sure you use raw if you want that not to happen) + let rendered = await renderLiquid("{% highlight 'nunjucks' %}{% raw %}{{ 'hello' }}{% endraw %}{% endhighlight %}"); + t.is(rendered, `
{{ 'hello' }}
`); +}); diff --git a/test/stubs-virtual/.gitkeep b/test/stubs-virtual/.gitkeep new file mode 100644 index 0000000..e69de29 From de2b73392f7963ae5dcb836ca8e8317cdc48ca63 Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 17 Oct 2025 22:07:29 -0500 Subject: [PATCH 06/10] Simplify markdown tests --- src/markdownSyntaxHighlightOptions.js | 52 ---------- syntaxHighlight.js | 35 ++++--- test/EleventyTest.js | 35 ++++--- test/MarkdownHighlightTest.js | 138 ++++++++++++-------------- test/issue-75/eleventy.config.js | 5 - test/issue-75/index.njk | 6 -- test/issue-80/eleventy.config.js | 5 - test/issue-80/index.md | 3 - 8 files changed, 107 insertions(+), 172 deletions(-) delete mode 100644 src/markdownSyntaxHighlightOptions.js delete mode 100644 test/issue-75/eleventy.config.js delete mode 100644 test/issue-75/index.njk delete mode 100644 test/issue-80/eleventy.config.js delete mode 100644 test/issue-80/index.md diff --git a/src/markdownSyntaxHighlightOptions.js b/src/markdownSyntaxHighlightOptions.js deleted file mode 100644 index e72173d..0000000 --- a/src/markdownSyntaxHighlightOptions.js +++ /dev/null @@ -1,52 +0,0 @@ -import Prism from "prismjs"; -import PrismLoader from "./PrismLoader.js"; -import HighlightLinesGroup from "./HighlightLinesGroup.js"; -import getAttributes from "./getAttributes.js"; - -export default function (options = {}) { - return function(str, language) { - if(!language) { - // empty string means defer to the upstream escaping code built into markdown lib. - return ""; - } - - - let split = language.split("/"); - if( split.length ) { - language = split.shift(); - } - - let html; - if(language === "text") { - html = str; - } else { - let loader = PrismLoader(language, options) - if(!loader) { - html = str; - } else { - html = Prism.highlight(str, loader, language); - } - } - - let hasHighlightNumbers = split.length > 0; - let highlights = new HighlightLinesGroup(split.join("/"), "/"); - let lines = html.split("\n"); - - // Trim last line if it is empty - if (lines[lines.length - 1] === "") { - lines = lines.slice(0, -1); - } - - if(options.alwaysWrapLineHighlights || hasHighlightNumbers) { - lines = lines.map(function(line, j) { - return highlights.getLineMarkup(j, line); - }); - } - - const context = { content: str, language: language, options: options }; - const preAttributes = getAttributes(options.preAttributes, context); - const codeAttributes = getAttributes(options.codeAttributes, context); - - return `${lines.join(options.lineSeparator || "
")}
`; - }; -}; diff --git a/syntaxHighlight.js b/syntaxHighlight.js index 9d36fc7..6238afa 100644 --- a/syntaxHighlight.js +++ b/syntaxHighlight.js @@ -3,7 +3,6 @@ import Prism from "prismjs"; import PrismLoader from "./src/PrismLoader.js"; import hasTemplateFormat from "./src/hasTemplateFormat.js"; import HighlightPairedShortcode from "./src/HighlightPairedShortcode.js"; -import markdownPrismJs from "./src/markdownSyntaxHighlightOptions.js"; export default function(eleventyConfig, options){ try { @@ -14,6 +13,7 @@ export default function(eleventyConfig, options){ options = Object.assign({ init: function({Prism}){}, lineSeparator: "\n", + lineHighlightSeparator: " ", errorOnInvalidLanguage: false, alwaysWrapLineHighlights: false, preAttributes: {}, @@ -25,31 +25,38 @@ export default function(eleventyConfig, options){ PrismLoader(language) } - function shortcodeFunction(content, args) { - // {% highlight "js 0 2-3" %} - let [language, ...highlightNumbers] = (args || "").split(" "); - return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); - } - - if( hasTemplateFormat(options.templateFormats, "liquid") ) { - eleventyConfig.addPairedLiquidShortcode("highlight", shortcodeFunction); + eleventyConfig.addPairedLiquidShortcode("highlight", function(content, args) { + // {% highlight "js 0 2-3" %} + let [language, ...highlightNumbers] = (args || "").split(" "); + return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); + }); } if( hasTemplateFormat(options.templateFormats, "njk") ) { - eleventyConfig.addPairedNunjucksShortcode("highlight", shortcodeFunction); + eleventyConfig.addPairedNunjucksShortcode("highlight", function(content, args) { + // {% highlight "js 0 2-3" %} + let [language, ...highlightNumbers] = (args || "").split(" "); + return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); + }); } if( hasTemplateFormat(options.templateFormats, "md") ) { - // ```js/0,2-3 - // TODO swap to amendLibrary - eleventyConfig.addMarkdownHighlighter(markdownPrismJs(options)); + eleventyConfig.amendLibrary("md", (mdLib) => { + mdLib.set({ + highlight: function(content, rawLanguage) { + // ```js/0,2-3 + let [language, ...highlightNumbers] = (rawLanguage || "").split("/"); + return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); + } + }) + }); } // we need to add this as many template languages (WebC) rely on JavaScript functions (not just 11ty.js) // Note the argument order for language, content (differs from shortcode) eleventyConfig.addJavaScriptFunction("highlight", (language, content, ...highlightLines) => { - return HighlightPairedShortcode(content, language, highlightLines.join(" "), options); + return HighlightPairedShortcode(content, language, (highlightLines || []).join(" "), options); }); options.init({Prism}) diff --git a/test/EleventyTest.js b/test/EleventyTest.js index 9b5211a..304c7b9 100644 --- a/test/EleventyTest.js +++ b/test/EleventyTest.js @@ -1,28 +1,35 @@ import test from "ava"; import Eleventy from '@11ty/eleventy'; +import syntaxHighlight from "../syntaxHighlight.js"; -function normalizeNewLines(str) { - return str.replace(/\r\n/g, "\n"); +async function render(inputfile, content) { + let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site/", { + config: function(eleventyConfig) { + eleventyConfig.addPlugin(syntaxHighlight); + eleventyConfig.addTemplate(inputfile, content); + } + }); + return elev.toJSON(); } test("Diff output escaped #75", async t => { - let elev = new Eleventy("./test/issue-75/", "./test/issue-75/_site/", { - configPath: "./test/issue-75/eleventy.config.js" - }); - let json = await elev.toJSON(); + let json = await render("issue-75.njk", `{% highlight "html" %} +

Hello

+{% endhighlight %} +{% highlight "diff-html" %} +-

Hello

+{% endhighlight %}`); t.is(json.length, 1); - t.is(normalizeNewLines(json[0].content.trim()), normalizeNewLines(`
<p>Hello</p>
-
-<p>Hello</p>
`)); + t.is(json[0].content.trim(), `
<p>Hello</p>
+
-<p>Hello</p>
`); }); test("diff-javascript #80", async t => { - let elev = new Eleventy("./test/issue-80/", "./test/issue-80/_site/", { - configPath: "./test/issue-80/eleventy.config.js" - }); - let json = await elev.toJSON(); + let json = await render("issue-80.md", `\`\`\`diff-javascript +- foo() +\`\`\``); t.is(json.length, 1); - t.is(normalizeNewLines(json[0].content.trim()), normalizeNewLines(`
- foo()
-
`)); + t.is(json[0].content.trim(), `
- foo()
`); }); diff --git a/test/MarkdownHighlightTest.js b/test/MarkdownHighlightTest.js index abbc2e0..6f0cbfe 100644 --- a/test/MarkdownHighlightTest.js +++ b/test/MarkdownHighlightTest.js @@ -1,88 +1,93 @@ import test from "ava"; -import md from "markdown-it"; -import markdownPrismJsOptions from "../src/markdownSyntaxHighlightOptions.js"; +import Eleventy from '@11ty/eleventy'; +import syntaxHighlight from "../syntaxHighlight.js"; -test("Test Markdown Highlighter", t => { - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) +async function renderMarkdown(content, pluginOptions = {}) { + let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site/", { + config: function(eleventyConfig) { + eleventyConfig.addPlugin(syntaxHighlight, pluginOptions); + eleventyConfig.addTemplate("test.md", content); + } }); - t.is(mdLib.render(`\`\`\`js + elev.disableLogger(); + return elev.toJSON(); +} + +test("Test Markdown Highlighter", async t => { + let [result] = await renderMarkdown(`\`\`\`js alert(); -\`\`\``).trim(), `
alert();
`); +\`\`\``, { + alwaysWrapLineHighlights: true }); -test("Test Markdown Highlighter No Line Highlights", t => { - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions() - }); - t.is(mdLib.render(`\`\`\`js + t.is(result.content.trim(), `
alert();
`); +}); + +test("Test Markdown Highlighter (lines)", async t => { + let [result] = await renderMarkdown(`\`\`\`js/0/1 +first(); +second(); +\`\`\``); + + t.is(result.content.trim(), `
first();\nsecond();
`); +}); + +test("Test Markdown Highlighter No Line Highlights", async t => { + let [result] = await renderMarkdown(`\`\`\`js alert(); -\`\`\``).trim(), `
alert();
`); +\`\`\``); + + t.is(result.content.trim(), `
alert();
`); }); -test("Markdown with `preAttributes`", t => { - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions({ - alwaysWrapLineHighlights: true, - preAttributes: { - // will override class="language-js" - class: ({language}) => "not-a-lang-" + language - } - }) - }); - t.is(mdLib.render(`\`\`\`js +test("Markdown with `preAttributes`", async t => { + let [result] = await renderMarkdown(`\`\`\`js alert(); -\`\`\``).trim(), `
alert();
`); +\`\`\``, { + alwaysWrapLineHighlights: true, + preAttributes: { + // will override class="language-js" + class: ({language}) => "not-a-lang-" + language + } }); -test("Test Njk Alias", t => { - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions() - }); - t.is(mdLib.render(`\`\`\`njk -{% raw %}hello{% endraw %} -\`\`\``).trim(), `
{% raw %}hello{% endraw %}
`); + t.is(result.content.trim(), `
alert();
`); }); -test("Test Nunjucks Alias", t => { - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions() - }); - t.is(mdLib.render(`\`\`\`nunjucks +test("Test Njk Alias", async t => { + let [result] = await renderMarkdown(`\`\`\`njk {% raw %}hello{% endraw %} -\`\`\``).trim(), `
{% raw %}hello{% endraw %}
`); +\`\`\``); + + t.is(result.content.trim(), `
hello
`); }); -test("Markdown Invalid language", t => { - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions({ - errorOnInvalidLanguage: true - }) - }); +test("Test Nunjucks Alias", async t => { + let [result] = await renderMarkdown(`\`\`\`nunjucks +{% raw %}hello{% endraw %} +\`\`\``); + + t.is(result.content.trim(), `
hello
`); +}); - t.throws(() => { - mdLib.render(`\`\`\`asldkjflksdaj +test("Markdown Invalid language", async t => { + await t.throwsAsync(async () => { + await renderMarkdown(`\`\`\`asldkjflksdaj hello -\`\`\``); +\`\`\``, { + errorOnInvalidLanguage: true +}); }); }); -test("Test loader invalid language with ignore", t => { - let src = `\`\`\`asldkjflksdaj +test("Test loader invalid language with ignore", async t => { + let src = `\`\`\`asldk9 hello \`\`\``; - let mdLib = md(); - mdLib.set({ - highlight: markdownPrismJsOptions() - }); - t.is(mdLib.render(src).trim(), `
hello
`); + let [result] = await renderMarkdown(src); + + t.is(result.content.trim(), `
hello
`); }); // test("Test Markdown Highlighter Block Comment", t => { @@ -96,16 +101,3 @@ hello // */ // \`\`\``).trim(), `
/*
* this is a string
*/
`); // }); - -// TODO this still ain’t working right with the line highlighter. -// test("Test Markdown Highlighter GraphQL Example", t => { -// let mdLib = md(); -// mdLib.set({ -// highlight: markdownPrismJsOptions({ alwaysWrapLineHighlights: true }) -// }); -// t.is(mdLib.render(`\`\`\`js -// var schema = buildSchema(\`type Query { -// hello: String -// }\`); -// \`\`\``).trim(), ``); -// }); diff --git a/test/issue-75/eleventy.config.js b/test/issue-75/eleventy.config.js deleted file mode 100644 index f04b28c..0000000 --- a/test/issue-75/eleventy.config.js +++ /dev/null @@ -1,5 +0,0 @@ -import syntaxHighlight from "../../syntaxHighlight.js"; - -export default function(eleventyConfig) { - eleventyConfig.addPlugin(syntaxHighlight); -}; diff --git a/test/issue-75/index.njk b/test/issue-75/index.njk deleted file mode 100644 index 86baee8..0000000 --- a/test/issue-75/index.njk +++ /dev/null @@ -1,6 +0,0 @@ -{% highlight "html" %} -

Hello

-{% endhighlight %} -{% highlight "diff-html" %} --

Hello

-{% endhighlight %} diff --git a/test/issue-80/eleventy.config.js b/test/issue-80/eleventy.config.js deleted file mode 100644 index f04b28c..0000000 --- a/test/issue-80/eleventy.config.js +++ /dev/null @@ -1,5 +0,0 @@ -import syntaxHighlight from "../../syntaxHighlight.js"; - -export default function(eleventyConfig) { - eleventyConfig.addPlugin(syntaxHighlight); -}; diff --git a/test/issue-80/index.md b/test/issue-80/index.md deleted file mode 100644 index 54f886e..0000000 --- a/test/issue-80/index.md +++ /dev/null @@ -1,3 +0,0 @@ -```diff-javascript -- foo() -``` From 52eed9df3275172a4e9a727f64d1ac100df83dc4 Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 17 Oct 2025 22:32:24 -0500 Subject: [PATCH 07/10] Fixes #77 --- syntaxHighlight.js | 30 +++++++++++++++++++----------- test/EleventyTest.js | 11 +++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/syntaxHighlight.js b/syntaxHighlight.js index 6238afa..a4f1c35 100644 --- a/syntaxHighlight.js +++ b/syntaxHighlight.js @@ -4,6 +4,8 @@ import PrismLoader from "./src/PrismLoader.js"; import hasTemplateFormat from "./src/hasTemplateFormat.js"; import HighlightPairedShortcode from "./src/HighlightPairedShortcode.js"; +const TRIPLE_BACKTICK = "```"; + export default function(eleventyConfig, options){ try { eleventyConfig.versionCheck(pkg["11ty"].compatibility); @@ -13,7 +15,6 @@ export default function(eleventyConfig, options){ options = Object.assign({ init: function({Prism}){}, lineSeparator: "\n", - lineHighlightSeparator: " ", errorOnInvalidLanguage: false, alwaysWrapLineHighlights: false, preAttributes: {}, @@ -25,20 +26,27 @@ export default function(eleventyConfig, options){ PrismLoader(language) } + function shortcode(content, args) { + // {% highlight "js 0 2-3" %} + let [language, ...highlightNumbers] = (args || "").split(" "); + + // Issue #77 avoid issues when nesting code blocks inside other elements (e.g.
{% highlight %}…
), introducing extra paragraphs + if(this.page.inputPath.endsWith(".md")) { + let highlightNumbersStr = highlightNumbers.length > 0 ? `/${highlightNumbers.join("/")}` : ""; + return `\n\n${TRIPLE_BACKTICK}${language || ""}${highlightNumbersStr} +${content} +${TRIPLE_BACKTICK}\n\n`; + } + + return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); + } + if( hasTemplateFormat(options.templateFormats, "liquid") ) { - eleventyConfig.addPairedLiquidShortcode("highlight", function(content, args) { - // {% highlight "js 0 2-3" %} - let [language, ...highlightNumbers] = (args || "").split(" "); - return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); - }); + eleventyConfig.addPairedLiquidShortcode("highlight", shortcode); } if( hasTemplateFormat(options.templateFormats, "njk") ) { - eleventyConfig.addPairedNunjucksShortcode("highlight", function(content, args) { - // {% highlight "js 0 2-3" %} - let [language, ...highlightNumbers] = (args || "").split(" "); - return HighlightPairedShortcode(content, language, highlightNumbers.join(" "), options); - }); + eleventyConfig.addPairedNunjucksShortcode("highlight", shortcode); } if( hasTemplateFormat(options.templateFormats, "md") ) { diff --git a/test/EleventyTest.js b/test/EleventyTest.js index 304c7b9..4800e35 100644 --- a/test/EleventyTest.js +++ b/test/EleventyTest.js @@ -33,3 +33,14 @@ test("diff-javascript #80", async t => { t.is(json.length, 1); t.is(json[0].content.trim(), `
- foo()
`); }); + +test("Issue #77", async t => { + let json = await render("issue-77.md", `
{% highlight "js" %} +// line 1 + +// line 3 +{% endhighlight %}
`); + + let content = json[0].content.trim(); + t.is(content, `
\n
// line 1\n\n// line 3
\n
`); +}); From 8d3b8893b70e5f81508eb0640f087346b075526e Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Wed, 29 Oct 2025 15:46:52 -0500 Subject: [PATCH 08/10] WebC v0.12+ compat --- syntax-highlight.webc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syntax-highlight.webc b/syntax-highlight.webc index d894ea3..54a0bbf 100644 --- a/syntax-highlight.webc +++ b/syntax-highlight.webc @@ -1,5 +1,5 @@ -