]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/unicode.rs
rustup https://github.com/rust-lang/rust/pull/67455
[rust.git] / clippy_lints / src / unicode.rs
1 use crate::utils::{is_allowed, snippet, span_lint_and_sugg};
2 use rustc::declare_lint_pass;
3 use rustc::hir::*;
4 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
5 use rustc_errors::Applicability;
6 use rustc_session::declare_tool_lint;
7 use syntax::ast::LitKind;
8 use syntax::source_map::Span;
9 use unicode_normalization::UnicodeNormalization;
10
11 declare_clippy_lint! {
12     /// **What it does:** Checks for the Unicode zero-width space in the code.
13     ///
14     /// **Why is this bad?** Having an invisible character in the code makes for all
15     /// sorts of April fools, but otherwise is very much frowned upon.
16     ///
17     /// **Known problems:** None.
18     ///
19     /// **Example:** You don't see it, but there may be a zero-width space
20     /// somewhere in this text.
21     pub ZERO_WIDTH_SPACE,
22     correctness,
23     "using a zero-width space in a string literal, which is confusing"
24 }
25
26 declare_clippy_lint! {
27     /// **What it does:** Checks for non-ASCII characters in string literals.
28     ///
29     /// **Why is this bad?** Yeah, we know, the 90's called and wanted their charset
30     /// back. Even so, there still are editors and other programs out there that
31     /// don't work well with Unicode. So if the code is meant to be used
32     /// internationally, on multiple operating systems, or has other portability
33     /// requirements, activating this lint could be useful.
34     ///
35     /// **Known problems:** None.
36     ///
37     /// **Example:**
38     /// ```rust
39     /// let x = String::from("€");
40     /// ```
41     /// Could be written as:
42     /// ```rust
43     /// let x = String::from("\u{20ac}");
44     /// ```
45     pub NON_ASCII_LITERAL,
46     pedantic,
47     "using any literal non-ASCII chars in a string literal instead of using the `\\u` escape"
48 }
49
50 declare_clippy_lint! {
51     /// **What it does:** Checks for string literals that contain Unicode in a form
52     /// that is not equal to its
53     /// [NFC-recomposition](http://www.unicode.org/reports/tr15/#Norm_Forms).
54     ///
55     /// **Why is this bad?** If such a string is compared to another, the results
56     /// may be surprising.
57     ///
58     /// **Known problems** None.
59     ///
60     /// **Example:** You may not see it, but "à"" and "à"" aren't the same string. The
61     /// former when escaped is actually `"a\u{300}"` while the latter is `"\u{e0}"`.
62     pub UNICODE_NOT_NFC,
63     pedantic,
64     "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)"
65 }
66
67 declare_lint_pass!(Unicode => [ZERO_WIDTH_SPACE, NON_ASCII_LITERAL, UNICODE_NOT_NFC]);
68
69 impl LateLintPass<'_, '_> for Unicode {
70     fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &'_ Expr) {
71         if let ExprKind::Lit(ref lit) = expr.kind {
72             if let LitKind::Str(_, _) = lit.node {
73                 check_str(cx, lit.span, expr.hir_id)
74             }
75         }
76     }
77 }
78
79 fn escape<T: Iterator<Item = char>>(s: T) -> String {
80     let mut result = String::new();
81     for c in s {
82         if c as u32 > 0x7F {
83             for d in c.escape_unicode() {
84                 result.push(d)
85             }
86         } else {
87             result.push(c);
88         }
89     }
90     result
91 }
92
93 fn check_str(cx: &LateContext<'_, '_>, span: Span, id: HirId) {
94     let string = snippet(cx, span, "");
95     if string.contains('\u{200B}') {
96         span_lint_and_sugg(
97             cx,
98             ZERO_WIDTH_SPACE,
99             span,
100             "zero-width space detected",
101             "consider replacing the string with",
102             string.replace("\u{200B}", "\\u{200B}"),
103             Applicability::MachineApplicable,
104         );
105     }
106     if string.chars().any(|c| c as u32 > 0x7F) {
107         span_lint_and_sugg(
108             cx,
109             NON_ASCII_LITERAL,
110             span,
111             "literal non-ASCII character detected",
112             "consider replacing the string with",
113             if is_allowed(cx, UNICODE_NOT_NFC, id) {
114                 escape(string.chars())
115             } else {
116                 escape(string.nfc())
117             },
118             Applicability::MachineApplicable,
119         );
120     }
121     if is_allowed(cx, NON_ASCII_LITERAL, id) && string.chars().zip(string.nfc()).any(|(a, b)| a != b) {
122         span_lint_and_sugg(
123             cx,
124             UNICODE_NOT_NFC,
125             span,
126             "non-NFC Unicode sequence detected",
127             "consider replacing the string with",
128             string.nfc().collect::<String>(),
129             Applicability::MachineApplicable,
130         );
131     }
132 }