diff --git a/src/rules/no-duplicate-definitions.js b/src/rules/no-duplicate-definitions.js index 70ef7cd3..0d1d5a40 100644 --- a/src/rules/no-duplicate-definitions.js +++ b/src/rules/no-duplicate-definitions.js @@ -7,7 +7,7 @@ // Imports //----------------------------------------------------------------------------- -import { normalizeIdentifier } from "micromark-util-normalize-identifier"; +import { normalizeIdentifier } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -81,13 +81,11 @@ export default /** @satisfies {NoDuplicateDefinitionsRuleDefinition} */ ({ create(context) { const allowDefinitions = new Set( - context.options[0].allowDefinitions.map(identifier => - normalizeIdentifier(identifier).toLowerCase(), - ), + context.options[0].allowDefinitions.map(normalizeIdentifier), ); const allowFootnoteDefinitions = new Set( - context.options[0].allowFootnoteDefinitions.map(identifier => - normalizeIdentifier(identifier).toLowerCase(), + context.options[0].allowFootnoteDefinitions.map( + normalizeIdentifier, ), ); const [{ checkFootnoteDefinitions }] = context.options; diff --git a/src/rules/no-empty-definitions.js b/src/rules/no-empty-definitions.js index 824734bd..9adbb068 100644 --- a/src/rules/no-empty-definitions.js +++ b/src/rules/no-empty-definitions.js @@ -7,8 +7,7 @@ // Imports //----------------------------------------------------------------------------- -import { normalizeIdentifier } from "micromark-util-normalize-identifier"; -import { htmlCommentPattern } from "../util.js"; +import { htmlCommentPattern, normalizeIdentifier } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -96,13 +95,11 @@ export default /** @satisfies {NoEmptyDefinitionsRuleDefinition} */ ({ create(context) { const allowDefinitions = new Set( - context.options[0].allowDefinitions.map(identifier => - normalizeIdentifier(identifier).toLowerCase(), - ), + context.options[0].allowDefinitions.map(normalizeIdentifier), ); const allowFootnoteDefinitions = new Set( - context.options[0].allowFootnoteDefinitions.map(identifier => - normalizeIdentifier(identifier).toLowerCase(), + context.options[0].allowFootnoteDefinitions.map( + normalizeIdentifier, ), ); const [{ checkFootnoteDefinitions }] = context.options; diff --git a/src/rules/no-reference-like-urls.js b/src/rules/no-reference-like-urls.js index 68b95203..794e8241 100644 --- a/src/rules/no-reference-like-urls.js +++ b/src/rules/no-reference-like-urls.js @@ -7,7 +7,7 @@ // Imports //----------------------------------------------------------------------------- -import { normalizeIdentifier } from "micromark-util-normalize-identifier"; +import { normalizeIdentifier } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -79,8 +79,7 @@ export default /** @satisfies {NoReferenceLikeUrlsRuleDefinition} */ ({ const { label, destination } = match.groups; const { type, title } = node; const prefix = type === "image" ? "!" : ""; - const url = - normalizeIdentifier(destination).toLowerCase(); + const url = normalizeIdentifier(destination); if (definitionIdentifiers.has(url)) { context.report({ diff --git a/src/rules/no-unused-definitions.js b/src/rules/no-unused-definitions.js index 71315032..7a231314 100644 --- a/src/rules/no-unused-definitions.js +++ b/src/rules/no-unused-definitions.js @@ -7,7 +7,7 @@ // Imports //----------------------------------------------------------------------------- -import { normalizeIdentifier } from "micromark-util-normalize-identifier"; +import { normalizeIdentifier } from "../util.js"; //----------------------------------------------------------------------------- // Type Definitions @@ -81,13 +81,11 @@ export default /** @satisfies {NoUnusedDefinitionsRuleDefinition} */ ({ create(context) { const allowDefinitions = new Set( - context.options[0].allowDefinitions.map(identifier => - normalizeIdentifier(identifier).toLowerCase(), - ), + context.options[0].allowDefinitions.map(normalizeIdentifier), ); const allowFootnoteDefinitions = new Set( - context.options[0].allowFootnoteDefinitions.map(identifier => - normalizeIdentifier(identifier).toLowerCase(), + context.options[0].allowFootnoteDefinitions.map( + normalizeIdentifier, ), ); const [{ checkFootnoteDefinitions }] = context.options; diff --git a/src/util.js b/src/util.js index b69e80eb..314ce671 100644 --- a/src/util.js +++ b/src/util.js @@ -3,6 +3,12 @@ * @author Nicholas C. Zakas */ +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import { normalizeIdentifier as micromarkUtilNormalizeIdentifier } from "micromark-util-normalize-identifier"; + //----------------------------------------------------------------------------- // Regex Patterns //----------------------------------------------------------------------------- @@ -48,6 +54,15 @@ export function frontmatterHasTitle(value, pattern) { return false; } +/** + * Normalizes a Markdown reference identifier. + * @param {string} identifier The identifier to normalize. + * @returns {string} The normalized lowercase identifier. + */ +export function normalizeIdentifier(identifier) { + return micromarkUtilNormalizeIdentifier(identifier).toLowerCase(); +} + /** * Replaces all HTML comments with whitespace. * This preserves offsets and locations of characters diff --git a/tests/util.test.js b/tests/util.test.js index ccaaf5f8..64e3bb87 100644 --- a/tests/util.test.js +++ b/tests/util.test.js @@ -8,7 +8,11 @@ //------------------------------------------------------------------------------ import assert from "node:assert"; -import { frontmatterHasTitle, stripHtmlComments } from "../src/util.js"; +import { + frontmatterHasTitle, + normalizeIdentifier, + stripHtmlComments, +} from "../src/util.js"; //------------------------------------------------------------------------------ // Tests @@ -72,6 +76,50 @@ describe("util", () => { }); }); + describe("normalizeIdentifier()", () => { + it("should preserve an already normalized identifier", () => { + const input = "example"; + const result = normalizeIdentifier(input); + assert.strictEqual(result, "example"); + }); + + it("should convert a mixed-case identifier to lowercase", () => { + const input = "ExAmPlE"; + const result = normalizeIdentifier(input); + assert.strictEqual(result, "example"); + }); + + it("should trim leading and trailing spaces", () => { + const input = " example "; + const result = normalizeIdentifier(input); + assert.strictEqual(result, "example"); + }); + + it("should collapse consecutive internal spaces into a single space", () => { + const input = "foo bar"; + const result = normalizeIdentifier(input); + assert.strictEqual(result, "foo bar"); + }); + + it("should normalize tabs and line breaks to a single space", () => { + const input = "foo\t\r\nbar"; + const result = normalizeIdentifier(input); + assert.strictEqual(result, "foo bar"); + }); + + it("should preserve an empty string", () => { + const input = ""; + const result = normalizeIdentifier(input); + assert.strictEqual(result, ""); + }); + + it("should return an empty string for Markdown whitespace only", () => { + const input = " \t\r\n "; + const result = normalizeIdentifier(input); + assert.strictEqual(result, ""); + }); + }); + describe("stripHtmlComments()", () => { it("should replace single-line HTML comments with spaces", () => { const input = "HelloWorld";