Skip to content

fix(cz-commitlint): add VS16 to single character emojis#4666

Merged
escapedcat merged 1 commit intoconventional-changelog:masterfrom
mrt181:fix/missing-VS16
Mar 17, 2026
Merged

fix(cz-commitlint): add VS16 to single character emojis#4666
escapedcat merged 1 commit intoconventional-changelog:masterfrom
mrt181:fix/missing-VS16

Conversation

@mrt181
Copy link
Copy Markdown
Contributor

@mrt181 mrt181 commented Mar 17, 2026

Description

Single character emojis like 🛠 or 🗑 do not align with 2 character emojis in the rendered menu.
This adds character VS16 to single column width emojis to align them with 2 column width emojis.

Motivation and Context

image

Usage examples

image
echo "fix(cz-commitlint): add VS16 to single character emojis" | npx commitlint # passes

How Has This Been Tested?

Added unit tests and used the local version to create the new Usage example.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add VS16 normalization for single-character emoji alignment

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Normalize single-character emojis by adding VS16 (U+FE0F) for consistent terminal alignment
• Implement emoji presentation detection using Unicode properties and grapheme segmentation
• Add comprehensive test coverage for emoji normalization with various emoji types
Diagram
flowchart LR
  A["Raw emoji input"] --> B["normalizeEmoji function"]
  B --> C["Detect emoji type"]
  C --> D1["Emoji_Presentation or has VS16/VS15"]
  C --> D2["Text-style emoji base"]
  D1 --> E["Return unchanged"]
  D2 --> F["Append VS16 U+FE0F"]
  E --> G["Aligned terminal display"]
  F --> G
Loading

Grey Divider

File Changes

1. @commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts 🐞 Bug fix +40/-1

Implement emoji normalization with VS16 support

• Added normalizeEmoji() function to append VS16 (U+FE0F) to text-style emojis for consistent
 terminal width
• Implemented Unicode property detection using Intl.Segmenter and regex patterns for emoji
 classification
• Integrated emoji normalization into the enum list mapping logic
• Handles edge cases: existing variation selectors, presentation-default emojis, and trailing
 whitespace

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts


2. @commitlint/cz-commitlint/src/services/getRuleQuestionConfig.test.ts 🧪 Tests +73/-3

Add comprehensive emoji normalization test coverage

• Added new test case for emoji normalization covering single-character emojis (🛠, 🗑) and
 presentation-default emojis (⚙️)
• Extended existing emojiInHeader test with additional emoji types to verify VS16 insertion
 behavior
• Added inline comments documenting emoji presentation properties and expected normalization
 behavior
• Verified that emojis with and without VS16 render with correct column alignment

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.test.ts


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 17, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Intl.Segmenter typings missing 🐞 Bug ✓ Correctness
Description
getRuleQuestionConfig.ts instantiates Intl.Segmenter, but @commitlint/cz-commitlint inherits
tsconfig.shared.json which sets compilerOptions.lib to only ["es2022"], so Intl.Segmenter is
not available in the configured TS standard library types. This will break TypeScript
compilation/typechecking for this package.
Code

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts[R17-19]

+const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
+const isPresentation = /^\p{Emoji_Presentation}/u;
+const isEmojiBase = /^\p{Emoji}/u;
Evidence
The PR introduces a new usage of Intl.Segmenter, while the package’s tsconfig extends a shared
config that restricts the TS lib to es2022 only; no package-level override adds additional libs.

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts[17-19]
@commitlint/cz-commitlint/tsconfig.json[1-11]
tsconfig.shared.json[1-5]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`@commitlint/cz-commitlint` now uses `Intl.Segmenter`, but the TypeScript project config only includes `lib: ["es2022"]`. This configuration does not provide typings for `Intl.Segmenter`, causing TypeScript compilation/typechecking failures.

## Issue Context
The package extends the monorepo shared tsconfig and does not override `compilerOptions.lib`.

## Fix Focus Areas
- tsconfig.shared.json[1-24]
- @commitlint/cz-commitlint/tsconfig.json[1-11]
- @commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts[17-19]

## Suggested fix
Prefer a package-local override to minimize monorepo-wide impact:
- In `@commitlint/cz-commitlint/tsconfig.json`, add a `compilerOptions.lib` that extends the existing lib with an Intl-capable lib that includes `Intl.Segmenter` (e.g. add the appropriate newer `es20xx`/`esnext` lib entries).

Alternative:
- Remove `Intl.Segmenter` usage and use a simpler code-point based check (`Array.from(...)`) since the feature is intended for *single-codepoint* emojis anyway.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. VS16 appended to clusters 🐞 Bug ✓ Correctness
Description
normalizeEmoji appends U+FE0F to the end of any single grapheme cluster that matches \p{Emoji}
but not \p{Emoji_Presentation}; for multi-codepoint clusters (e.g., skin-tone modifiers or ZWJ
sequences), VS16 is appended after the whole cluster rather than after the base emoji code point.
This can fail to force emoji presentation and can render incorrectly in the interactive menu.
Code

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts[R33-49]

+	const segments = Array.from(segmenter.segment(trimmed));
+
+	if (segments.length === 1) {
+		const char = segments[0].segment;
+
+		switch (true) {
+			case char.includes("\uFE0F"):
+			case char.includes("\uFE0E"):
+				return emoji;
+
+			case isPresentation.test(char):
+				return emoji;
+
+			// Is it a "Text-style" emoji base and not a number? Add VS16!
+			case isEmojiBase.test(char) && !/^[0-9#*]$/.test(char):
+				return trimmed + "\uFE0F" + trailing;
+		}
Evidence
The code uses grapheme segmentation (so ZWJ/skin-tone sequences are often a single segment) and then
appends VS16 to the entire trimmed string. Prompt config emoji is an unconstrained string and is
stored without validation, so multi-codepoint emoji sequences are valid inputs and will flow into
normalizeEmoji.

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts[33-49]
@commitlint/types/src/prompt.ts[18-40]
@commitlint/cz-commitlint/src/store/prompts.ts[14-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`normalizeEmoji()` currently appends U+FE0F to the end of any single *grapheme cluster* that is `\p{Emoji}` but not `\p{Emoji_Presentation}`. Grapheme clusters can be multi-codepoint (ZWJ sequences, skin-tone modifiers), and appending VS16 at the end is the wrong placement.

## Issue Context
`emoji?: string` is user-provided and unconstrained; questions are stored as-is, so multi-codepoint emoji sequences can reach `normalizeEmoji()`.

## Fix Focus Areas
- @commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts[27-53]
- @commitlint/cz-commitlint/src/store/prompts.ts[14-28]
- @commitlint/types/src/prompt.ts[18-40]

## Suggested fix
Implement one of:
1) **Restrict normalization to single code point** (aligns with PR goal):
  - After trimming trailing whitespace, ensure `Array.from(trimmed).length === 1` before considering adding VS16.
  - Also ensure the string does not contain ZWJ (`\u200D`) or modifiers.

2) **Insert VS16 after the base code point**:
  - `const cps = Array.from(trimmed); return cps[0] + '\uFE0F' + cps.slice(1).join('') + trailing;`
  - Keep existing early-returns for already-present VS15/VS16.

Add a unit test covering an emoji with skin-tone or ZWJ (e.g., `"☝🏽"` or `"👨‍💻"`) asserting it is not modified (option 1) or that VS16 placement is correct (option 2).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@codesandbox-ci
Copy link
Copy Markdown

codesandbox-ci Bot commented Mar 17, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Comment thread @commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the @commitlint/cz-commitlint interactive enum prompt formatting by normalizing certain emojis to use emoji presentation (VS16), avoiding terminal column misalignment when some emojis render at text-width.

Changes:

  • Add an emoji normalizer that appends VS16 (U+FE0F) for single-grapheme emojis that are \p{Emoji} but not \p{Emoji_Presentation} and don’t already specify a variation selector.
  • Apply normalization when building enum list display strings.
  • Extend unit tests to cover VS16 normalization behavior (including when emojiInHeader is enabled).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts Introduces normalizeEmoji() and applies it to enum emoji rendering.
@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.test.ts Adds/updates tests asserting VS16 normalization and adjusted spacing expectations.
Comments suppressed due to low confidence (1)

@commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts:110

  • The normalization currently affects value when emojiInHeader is true, meaning the selected enum value (and therefore the committed header) can change from the emoji configured by the user (e.g. 🛠 -> 🛠️ via VS16). If the intent is only to fix prompt alignment, consider using the normalized emoji for name/display but keeping value based on the raw configured emoji (trimmed), or make this behavior opt-in to avoid a breaking change for existing configs.
					if (enumDescription) {
						const rawEmoji = enumDescriptions[enumName]?.emoji;
						const emoji = rawEmoji ? normalizeEmoji(rawEmoji) : rawEmoji;

						const emojiPrefix = emoji
							? `${emoji}  `
							: hasConsistentEmojiUsage
								? ""
								: "    ";

						const paddedName = `${enumName}:`.padEnd(longest + 4);

						const name = `${emojiPrefix}${paddedName}${enumDescription}`;

						const value =
							emojiInHeader && emoji ? `${emoji.trim()} ${enumName}` : enumName;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread @commitlint/cz-commitlint/src/services/getRuleQuestionConfig.ts Outdated
Single character emojis like 🛠 or 🗑 do not align with 2 character
emojis in the rendered menu.
@mrt181 mrt181 force-pushed the fix/missing-VS16 branch from 6fdf099 to b89b000 Compare March 17, 2026 12:19
@escapedcat escapedcat merged commit 9e3e2d3 into conventional-changelog:master Mar 17, 2026
16 checks passed
This was referenced Mar 31, 2026
This was referenced Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants