Escape Control Char In Literal
ID |
csharp.escape_control_char_in_literal |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
code_smell, readability, string |
Description
Reports a regular string literal that holds a raw control character or an invisible character — an ASCII control, any Unicode whitespace other than the plain space, or a zero-width character such as the zero-width space, the word joiner or the byte order mark — where the escape sequence for that character should have been written instead.
Rationale
These characters get into source by accident: a tab copied out of a terminal, a non-breaking space pasted from a document or a web page, an ideographic space typed by an input method. Nothing marks them on screen, so the literal no longer matches what the reader sees. A comparison against the visually identical string fails, a dictionary lookup never hits, a format string splits on the wrong character, and the review that should have caught it saw only whitespace.
They are also fragile. Re-indentation, a reformat, or a round trip through a tool that trims trailing whitespace can silently change or delete the character, so the same source produces different behaviour before and after an unrelated edit. Written as an escape sequence, the same character is explicit, greppable, and survives every editor.
Raw characters are distinguished from their escaped spelling by reading the literal’s own text: an escaped tab and a raw tab decode to the same string, so only the way it was written can tell them apart. Because every escape sequence is made of printable ASCII characters, anything invisible found in the literal as written was typed raw.
Verbatim strings (@"…") and raw string literals are excluded. They provide no escape sequences
as an alternative and are meant to span lines, so raw whitespace inside them is how they are
supposed to be written. Interpolated strings are not covered either, because their text is carried
in interpolation tokens rather than in a string literal.
The range U+0080 to U+009F is also excluded. Those code points are control characters in Unicode, but they are not universally control data: the legacy single-byte encodings put printable characters there, so a literal that came through such an encoding would be reported for a character its author can see. U+007F is still reported.
In the example below, the placeholders <TAB>, <NBSP> and <ZWSP> stand for the character
itself, typed straight into the literal — which is exactly what makes the defect invisible in a
real file.
public class Parser
{
private const string Columns = "id<TAB>name"; // FLAW — a raw tab
private const string Amount = "1<NBSP>000"; // FLAW — a raw non-breaking space
private const string Sku = "AB<ZWSP>12"; // FLAW — a raw zero-width space
private const string Escaped = "id\tname"; // OK — escaped, and visible as such
private const string Plain = "id name"; // OK — a plain space
}
The escaped form of the second literal is the code point escape for U+00A0, which reads as
1, backslash, u00A0, 000 — five visible characters where there was one invisible one.
Remediation
Replace the character with its escape sequence. A tab is \t; the other named controls are \0,
\a, \b, \f and \v. Everything else takes a code point escape, written as a backslash, u,
and four hex digits — u00A0 for a non-breaking space, u3000 for an ideographic space, u200B
for a zero-width space. If the character got in by accident, delete it or put a plain space in
its place. When the literal holds a separator that more than one place needs, lift it to a named
constant so there is a single spot to inspect.