diff --git a/playground.nix b/playground.nix index 775566d..caa1877 100644 --- a/playground.nix +++ b/playground.nix @@ -11,7 +11,7 @@ pkgs.buildNpmPackage { src = ./playground; - npmDepsHash = "sha256-dUqllE2sz39VuF1++jNSdahM4uOeU87GFJe6OLnnV5A="; + npmDepsHash = "sha256-6iUjz6heVP5CNlRHA6eFPUr6BvONuzNlIY+mfpf4FP0="; nativeBuildInputs = [ pkgs.wasm-bindgen-cli diff --git a/playground/package-lock.json b/playground/package-lock.json index 1cba1a3..5cd62b0 100644 --- a/playground/package-lock.json +++ b/playground/package-lock.json @@ -1010,9 +1010,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { diff --git a/playground/tests/dependencies/source-maps.test.mjs b/playground/tests/dependencies/source-maps.test.mjs new file mode 100644 index 0000000..99c94ee --- /dev/null +++ b/playground/tests/dependencies/source-maps.test.mjs @@ -0,0 +1,47 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { createRequire } from "node:module"; +import postcss from "postcss"; + +// Resolve the same consumer PostCSS uses, including if npm nests the dependency. +const require = createRequire(import.meta.resolve("postcss")); +const { SourceMapConsumer } = require("source-map-js"); + +test("chained CSS transforms retain original locations and Unicode source content", async () => { + const input = '.course::before {\n content: "Näytä 📚";\n margin: 0;\n}\n'; + const first = await postcss([{ + postcssPlugin: "rename-course-selector", + Rule(rule) { rule.selector = ".course-material::before"; }, + }]).process(input, { from: "course.css", to: "intermediate.css", map: { inline: false } }); + const second = await postcss([{ + postcssPlugin: "expand-course-spacing", + Declaration(decl) { if (decl.prop === "margin") decl.value = "1rem"; }, + }]).process(first.css, { + from: "intermediate.css", to: "built.css", + map: { inline: false, prev: first.map.toJSON() }, + }); + assert.match(second.css, /\.course-material::before/); + assert.match(second.css, /margin: 1rem/); + const lines = second.css.split("\n"); + const line = lines.findIndex((text) => text.includes("margin:")); + const consumer = new SourceMapConsumer(second.map.toJSON()); + const original = consumer.originalPositionFor({ line: line + 1, column: lines[line].indexOf("margin:") }); + assert.match(original.source, /(^|\/)course\.css$/); + assert.equal(original.line, 3); + assert.equal(original.column, 2); + assert.equal(consumer.sourceContentFor(original.source), input); +}); + +// 1.2.2 rejects invalid indexed offsets before they can expand into huge maps. +// Only construct the consumer: never attempt the expensive vulnerable expansion. +test("indexed source maps reject invalid section offsets before processing", () => { + for (const line of [10_000_001, -1, 1.5, Infinity]) { + assert.throws(() => new SourceMapConsumer({ + version: 3, + sections: [{ + offset: { line, column: 0 }, + map: { version: 3, sources: ["course.css"], names: [], mappings: "AAAA" }, + }], + }), /offset/i); + } +});