Refactor tokens-based rules to take an &mut Vec<Diagnostic> (#5525)

This commit is contained in:
Charlie Marsh
2023-07-05 19:21:42 -04:00
committed by GitHub
parent 23363cafd1
commit 5dff3195d4
18 changed files with 113 additions and 154 deletions

View File

@@ -2,7 +2,7 @@
use anyhow::{bail, Result};
use ruff_text_size::{TextLen, TextRange, TextSize};
use rustpython_parser::ast::{self, ExceptHandler, Expr, Keyword, Ranged, Stmt};
use rustpython_parser::{lexer, Mode, Tok};
use rustpython_parser::{lexer, Mode};
use ruff_diagnostics::Edit;
use ruff_python_ast::helpers;
@@ -98,7 +98,7 @@ pub(crate) fn remove_argument(
// Case 1: there is only one argument.
let mut count = 0u32;
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if matches!(tok, Tok::Lpar) {
if tok.is_lpar() {
if count == 0 {
fix_start = Some(if remove_parentheses {
range.start()
@@ -109,7 +109,7 @@ pub(crate) fn remove_argument(
count = count.saturating_add(1);
}
if matches!(tok, Tok::Rpar) {
if tok.is_rpar() {
count = count.saturating_sub(1);
if count == 0 {
fix_end = Some(if remove_parentheses {
@@ -131,11 +131,11 @@ pub(crate) fn remove_argument(
let mut seen_comma = false;
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
if seen_comma {
if matches!(tok, Tok::NonLogicalNewline) {
if tok.is_non_logical_newline() {
// Also delete any non-logical newlines after the comma.
continue;
}
fix_end = Some(if matches!(tok, Tok::Newline) {
fix_end = Some(if tok.is_newline() {
range.end()
} else {
range.start()
@@ -145,7 +145,7 @@ pub(crate) fn remove_argument(
if range.start() == expr_range.start() {
fix_start = Some(range.start());
}
if fix_start.is_some() && matches!(tok, Tok::Comma) {
if fix_start.is_some() && tok.is_comma() {
seen_comma = true;
}
}
@@ -157,7 +157,7 @@ pub(crate) fn remove_argument(
fix_end = Some(expr_range.end());
break;
}
if matches!(tok, Tok::Comma) {
if tok.is_comma() {
fix_start = Some(range.start());
}
}

View File

@@ -3,6 +3,9 @@
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::{Indexer, Locator};
use crate::directives::TodoComment;
use crate::lex::docstring_detection::StateMachine;
use crate::registry::{AsRule, Rule};
@@ -12,8 +15,6 @@ use crate::rules::{
flake8_todos, pycodestyle, pylint, pyupgrade, ruff,
};
use crate::settings::Settings;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::{Indexer, Locator};
pub(crate) fn check_tokens(
locator: &Locator,
@@ -88,10 +89,11 @@ pub(crate) fn check_tokens(
};
if matches!(tok, Tok::String { .. } | Tok::Comment(_)) {
diagnostics.extend(ruff::rules::ambiguous_unicode_character(
ruff::rules::ambiguous_unicode_character(
&mut diagnostics,
locator,
range,
if matches!(tok, Tok::String { .. }) {
if tok.is_string() {
if is_docstring {
Context::Docstring
} else {
@@ -101,93 +103,77 @@ pub(crate) fn check_tokens(
Context::Comment
},
settings,
));
);
}
}
}
// ERA001
if enforce_commented_out_code {
diagnostics.extend(eradicate::rules::commented_out_code(
locator, indexer, settings,
));
eradicate::rules::commented_out_code(&mut diagnostics, locator, indexer, settings);
}
// W605
if enforce_invalid_escape_sequence {
for (tok, range) in tokens.iter().flatten() {
if matches!(tok, Tok::String { .. }) {
diagnostics.extend(pycodestyle::rules::invalid_escape_sequence(
if tok.is_string() {
pycodestyle::rules::invalid_escape_sequence(
&mut diagnostics,
locator,
*range,
settings.rules.should_fix(Rule::InvalidEscapeSequence),
));
);
}
}
}
// PLE2510, PLE2512, PLE2513
if enforce_invalid_string_character {
for (tok, range) in tokens.iter().flatten() {
if matches!(tok, Tok::String { .. }) {
diagnostics.extend(
pylint::rules::invalid_string_characters(locator, *range)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
if tok.is_string() {
pylint::rules::invalid_string_characters(&mut diagnostics, *range, locator);
}
}
}
// E701, E702, E703
if enforce_compound_statements {
diagnostics.extend(
pycodestyle::rules::compound_statements(tokens, locator, indexer, settings)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
pycodestyle::rules::compound_statements(
&mut diagnostics,
tokens,
locator,
indexer,
settings,
);
}
// Q001, Q002, Q003
if enforce_quotes {
diagnostics.extend(
flake8_quotes::rules::from_tokens(tokens, locator, settings)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
flake8_quotes::rules::from_tokens(&mut diagnostics, tokens, locator, settings);
}
// ISC001, ISC002
if enforce_implicit_string_concatenation {
diagnostics.extend(
flake8_implicit_str_concat::rules::implicit(
tokens,
&settings.flake8_implicit_str_concat,
locator,
)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
flake8_implicit_str_concat::rules::implicit(
&mut diagnostics,
tokens,
&settings.flake8_implicit_str_concat,
locator,
);
}
// COM812, COM818, COM819
if enforce_trailing_comma {
diagnostics.extend(
flake8_commas::rules::trailing_commas(tokens, locator, settings)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
flake8_commas::rules::trailing_commas(&mut diagnostics, tokens, locator, settings);
}
// UP034
if enforce_extraneous_parenthesis {
diagnostics.extend(
pyupgrade::rules::extraneous_parentheses(tokens, locator, settings).into_iter(),
);
pyupgrade::rules::extraneous_parentheses(&mut diagnostics, tokens, locator, settings);
}
// PYI033
if enforce_type_comment_in_stub && is_stub {
diagnostics.extend(flake8_pyi::rules::type_comment_in_stub(locator, indexer));
flake8_pyi::rules::type_comment_in_stub(&mut diagnostics, locator, indexer);
}
// TD001, TD002, TD003, TD004, TD005, TD006, TD007
@@ -203,18 +189,12 @@ pub(crate) fn check_tokens(
})
.collect();
diagnostics.extend(
flake8_todos::rules::todos(&todo_comments, locator, indexer, settings)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
flake8_todos::rules::todos(&mut diagnostics, &todo_comments, locator, indexer, settings);
diagnostics.extend(
flake8_fixme::rules::todos(&todo_comments)
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
flake8_fixme::rules::todos(&mut diagnostics, &todo_comments);
}
diagnostics.retain(|diagnostic| settings.rules.enabled(diagnostic.kind.rule()));
diagnostics
}

View File

@@ -48,12 +48,11 @@ fn is_standalone_comment(line: &str) -> bool {
/// ERA001
pub(crate) fn commented_out_code(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
indexer: &Indexer,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
for range in indexer.comment_ranges() {
let line = locator.full_lines(*range);
@@ -69,6 +68,4 @@ pub(crate) fn commented_out_code(
diagnostics.push(diagnostic);
}
}
diagnostics
}

View File

@@ -222,12 +222,11 @@ impl AlwaysAutofixableViolation for ProhibitedTrailingComma {
/// COM812, COM818, COM819
pub(crate) fn trailing_commas(
diagnostics: &mut Vec<Diagnostic>,
tokens: &[LexResult],
locator: &Locator,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
let tokens = tokens
.iter()
.flatten()
@@ -387,6 +386,4 @@ pub(crate) fn trailing_commas(
stack.pop();
}
}
diagnostics
}

View File

@@ -39,18 +39,19 @@ impl Violation for LineContainsHack {
}
}
pub(crate) fn todos(directive_ranges: &[TodoComment]) -> Vec<Diagnostic> {
directive_ranges
.iter()
.map(|TodoComment { directive, .. }| match directive.kind {
// FIX001
TodoDirectiveKind::Fixme => Diagnostic::new(LineContainsFixme, directive.range),
// FIX002
TodoDirectiveKind::Hack => Diagnostic::new(LineContainsHack, directive.range),
// FIX003
TodoDirectiveKind::Todo => Diagnostic::new(LineContainsTodo, directive.range),
// FIX004
TodoDirectiveKind::Xxx => Diagnostic::new(LineContainsXxx, directive.range),
})
.collect::<Vec<Diagnostic>>()
pub(crate) fn todos(diagnostics: &mut Vec<Diagnostic>, directive_ranges: &[TodoComment]) {
diagnostics.extend(
directive_ranges
.iter()
.map(|TodoComment { directive, .. }| match directive.kind {
// FIX001
TodoDirectiveKind::Fixme => Diagnostic::new(LineContainsFixme, directive.range),
// FIX002
TodoDirectiveKind::Hack => Diagnostic::new(LineContainsHack, directive.range),
// FIX003
TodoDirectiveKind::Todo => Diagnostic::new(LineContainsTodo, directive.range),
// FIX004
TodoDirectiveKind::Xxx => Diagnostic::new(LineContainsXxx, directive.range),
}),
);
}

View File

@@ -1,7 +1,6 @@
use itertools::Itertools;
use ruff_text_size::TextRange;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -91,21 +90,20 @@ impl Violation for MultiLineImplicitStringConcatenation {
/// ISC001, ISC002
pub(crate) fn implicit(
diagnostics: &mut Vec<Diagnostic>,
tokens: &[LexResult],
settings: &Settings,
locator: &Locator,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
for ((a_tok, a_range), (b_tok, b_range)) in tokens
.iter()
.flatten()
.filter(|(tok, _)| {
!matches!(tok, Tok::Comment(..))
&& (settings.allow_multiline || !matches!(tok, Tok::NonLogicalNewline))
!tok.is_comment() && (settings.allow_multiline || !tok.is_non_logical_newline())
})
.tuple_windows()
{
if matches!(a_tok, Tok::String { .. }) && matches!(b_tok, Tok::String { .. }) {
if a_tok.is_string() && b_tok.is_string() {
if locator.contains_line_break(TextRange::new(a_range.end(), b_range.start())) {
diagnostics.push(Diagnostic::new(
MultiLineImplicitStringConcatenation,
@@ -125,7 +123,6 @@ pub(crate) fn implicit(
};
};
}
diagnostics
}
fn concatenate_strings(a_range: TextRange, b_range: TextRange, locator: &Locator) -> Option<Fix> {

View File

@@ -34,9 +34,11 @@ impl Violation for TypeCommentInStub {
}
/// PYI033
pub(crate) fn type_comment_in_stub(locator: &Locator, indexer: &Indexer) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
pub(crate) fn type_comment_in_stub(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
indexer: &Indexer,
) {
for range in indexer.comment_ranges() {
let comment = locator.slice(*range);
@@ -44,8 +46,6 @@ pub(crate) fn type_comment_in_stub(locator: &Locator, indexer: &Indexer) -> Vec<
diagnostics.push(Diagnostic::new(TypeCommentInStub, *range));
}
}
diagnostics
}
static TYPE_COMMENT_REGEX: Lazy<Regex> =

View File

@@ -464,12 +464,11 @@ fn strings(locator: &Locator, sequence: &[TextRange], settings: &Settings) -> Ve
/// Generate `flake8-quote` diagnostics from a token stream.
pub(crate) fn from_tokens(
diagnostics: &mut Vec<Diagnostic>,
lxr: &[LexResult],
locator: &Locator,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
// Keep track of sequences of strings, which represent implicit string
// concatenation, and should thus be handled as a single unit.
let mut sequence = vec![];
@@ -488,7 +487,7 @@ pub(crate) fn from_tokens(
diagnostics.push(diagnostic);
}
} else {
if matches!(tok, Tok::String { .. }) {
if tok.is_string() {
// If this is a string, add it to the sequence.
sequence.push(range);
} else if !matches!(tok, Tok::Comment(..) | Tok::NonLogicalNewline) {
@@ -506,6 +505,4 @@ pub(crate) fn from_tokens(
diagnostics.extend(strings(locator, &sequence, settings));
sequence.clear();
}
diagnostics
}

View File

@@ -235,13 +235,12 @@ static ISSUE_LINK_REGEX_SET: Lazy<RegexSet> = Lazy::new(|| {
});
pub(crate) fn todos(
diagnostics: &mut Vec<Diagnostic>,
todo_comments: &[TodoComment],
locator: &Locator,
indexer: &Indexer,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
) {
for todo_comment in todo_comments {
let TodoComment {
directive,
@@ -256,8 +255,8 @@ pub(crate) fn todos(
continue;
}
directive_errors(directive, &mut diagnostics, settings);
static_errors(&mut diagnostics, content, range, directive);
directive_errors(diagnostics, directive, settings);
static_errors(diagnostics, content, range, directive);
let mut has_issue_link = false;
let mut curr_range = range;
@@ -297,14 +296,12 @@ pub(crate) fn todos(
diagnostics.push(Diagnostic::new(MissingTodoLink, directive.range));
}
}
diagnostics
}
/// Check that the directive itself is valid. This function modifies `diagnostics` in-place.
fn directive_errors(
directive: &TodoDirective,
diagnostics: &mut Vec<Diagnostic>,
directive: &TodoDirective,
settings: &Settings,
) {
if directive.content == "TODO" {

View File

@@ -100,13 +100,12 @@ impl AlwaysAutofixableViolation for UselessSemicolon {
/// E701, E702, E703
pub(crate) fn compound_statements(
diagnostics: &mut Vec<Diagnostic>,
lxr: &[LexResult],
locator: &Locator,
indexer: &Indexer,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
// Track the last seen instance of a variety of tokens.
let mut colon = None;
let mut semi = None;
@@ -311,6 +310,4 @@ pub(crate) fn compound_statements(
_ => {}
};
}
diagnostics
}

View File

@@ -40,25 +40,24 @@ impl AlwaysAutofixableViolation for InvalidEscapeSequence {
/// W605
pub(crate) fn invalid_escape_sequence(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
range: TextRange,
autofix: bool,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
let text = locator.slice(range);
// Determine whether the string is single- or triple-quoted.
let Some(leading_quote) = leading_quote(text) else {
return diagnostics;
return;
};
let Some(trailing_quote) = trailing_quote(text) else {
return diagnostics;
return;
};
let body = &text[leading_quote.len()..text.len() - trailing_quote.len()];
if leading_quote.contains(['r', 'R']) {
return diagnostics;
return;
}
let start_offset = range.start() + TextSize::try_from(leading_quote.len()).unwrap();
@@ -67,6 +66,7 @@ pub(crate) fn invalid_escape_sequence(
let mut contains_valid_escape_sequence = false;
let mut invalid_escape_sequence = Vec::new();
while let Some((i, c)) = chars_iter.next() {
if c != '\\' {
continue;
@@ -122,14 +122,13 @@ pub(crate) fn invalid_escape_sequence(
let location = start_offset + TextSize::try_from(i).unwrap();
let range = TextRange::at(location, next_char.text_len() + TextSize::from(1));
let diagnostic = Diagnostic::new(InvalidEscapeSequence(*next_char), range);
diagnostics.push(diagnostic);
invalid_escape_sequence.push(Diagnostic::new(InvalidEscapeSequence(*next_char), range));
}
if autofix {
if contains_valid_escape_sequence {
// Escape with backslash.
for diagnostic in &mut diagnostics {
for diagnostic in &mut invalid_escape_sequence {
diagnostic.set_fix(Fix::automatic(Edit::insertion(
r"\".to_string(),
diagnostic.range().start() + TextSize::from(1),
@@ -137,7 +136,7 @@ pub(crate) fn invalid_escape_sequence(
}
} else {
// Turn into raw string.
for diagnostic in &mut diagnostics {
for diagnostic in &mut invalid_escape_sequence {
// If necessary, add a space between any leading keyword (`return`, `yield`,
// `assert`, etc.) and the string. For example, `return"foo"` is valid, but
// `returnr"foo"` is not.
@@ -159,5 +158,5 @@ pub(crate) fn invalid_escape_sequence(
}
}
diagnostics
diagnostics.extend(invalid_escape_sequence);
}

View File

@@ -1,7 +1,7 @@
use anyhow::{bail, Ok, Result};
use ruff_text_size::TextRange;
use rustpython_parser::ast::{ExceptHandler, Expr, Ranged};
use rustpython_parser::{lexer, Mode, Tok};
use rustpython_parser::{lexer, Mode};
use ruff_diagnostics::Edit;
use ruff_python_ast::source_code::{Locator, Stylist};
@@ -10,7 +10,7 @@ use crate::autofix::codemods::CodegenStylist;
use crate::cst::matchers::{match_call_mut, match_dict, match_expression};
/// Generate a [`Edit`] to remove unused keys from format dict.
pub(crate) fn remove_unused_format_arguments_from_dict(
pub(super) fn remove_unused_format_arguments_from_dict(
unused_arguments: &[usize],
stmt: &Expr,
locator: &Locator,
@@ -35,7 +35,7 @@ pub(crate) fn remove_unused_format_arguments_from_dict(
}
/// Generate a [`Edit`] to remove unused keyword arguments from a `format` call.
pub(crate) fn remove_unused_keyword_arguments_from_format_call(
pub(super) fn remove_unused_keyword_arguments_from_format_call(
unused_arguments: &[usize],
location: TextRange,
locator: &Locator,
@@ -102,10 +102,10 @@ pub(crate) fn remove_exception_handler_assignment(
for (tok, range) in
lexer::lex_starts_at(contents, Mode::Module, except_handler.start()).flatten()
{
if matches!(tok, Tok::As) {
if tok.is_as() {
fix_start = prev;
}
if matches!(tok, Tok::Colon) {
if tok.is_colon() {
fix_end = Some(range.start());
break;
}

View File

@@ -4,7 +4,7 @@ use ruff_text_size::TextRange;
use rustc_hash::FxHashMap;
use rustpython_format::cformat::{CFormatPart, CFormatSpec, CFormatStrOrBytes, CFormatString};
use rustpython_parser::ast::{self, Constant, Expr, Ranged};
use rustpython_parser::{lexer, Mode, Tok};
use rustpython_parser::{lexer, Mode};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -205,9 +205,9 @@ pub(crate) fn bad_string_format_type(checker: &mut Checker, expr: &Expr, right:
let content = checker.locator.slice(expr.range());
let mut strings: Vec<TextRange> = vec![];
for (tok, range) in lexer::lex_starts_at(content, Mode::Module, expr.start()).flatten() {
if matches!(tok, Tok::String { .. }) {
if tok.is_string() {
strings.push(range);
} else if matches!(tok, Tok::Percent) {
} else if tok.is_percent() {
// Break as soon as we find the modulo symbol.
break;
}

View File

@@ -171,8 +171,11 @@ impl AlwaysAutofixableViolation for InvalidCharacterZeroWidthSpace {
}
/// PLE2510, PLE2512, PLE2513, PLE2514, PLE2515
pub(crate) fn invalid_string_characters(locator: &Locator, range: TextRange) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
pub(crate) fn invalid_string_characters(
diagnostics: &mut Vec<Diagnostic>,
range: TextRange,
locator: &Locator,
) {
let text = locator.slice(range);
for (column, match_) in text.match_indices(&['\x08', '\x1A', '\x1B', '\0', '\u{200b}']) {
@@ -195,6 +198,4 @@ pub(crate) fn invalid_string_characters(locator: &Locator, range: TextRange) ->
Edit::range_replacement(replacement.to_string(), range),
)));
}
diagnostics
}

View File

@@ -134,21 +134,21 @@ fn match_extraneous_parentheses(tokens: &[LexResult], mut i: usize) -> Option<(u
/// UP034
pub(crate) fn extraneous_parentheses(
diagnostics: &mut Vec<Diagnostic>,
tokens: &[LexResult],
locator: &Locator,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
let mut i = 0;
while i < tokens.len() {
if matches!(tokens[i], Ok((Tok::Lpar, _))) {
if let Some((start, end)) = match_extraneous_parentheses(tokens, i) {
i = end + 1;
let Ok((_, start_range)) = &tokens[start] else {
return diagnostics;
return;
};
let Ok((.., end_range)) = &tokens[end] else {
return diagnostics;
return;
};
let mut diagnostic = Diagnostic::new(
ExtraneousParentheses,
@@ -171,5 +171,4 @@ pub(crate) fn extraneous_parentheses(
i += 1;
}
}
diagnostics
}

View File

@@ -344,7 +344,7 @@ pub(crate) fn printf_string_formatting(
)
.flatten()
{
if matches!(tok, Tok::String { .. }) {
if tok.is_string() {
strings.push(range);
} else if matches!(tok, Tok::Rpar) {
// If we hit a right paren, we have to preserve it.

View File

@@ -3,7 +3,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Result};
use ruff_text_size::TextSize;
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
use rustpython_parser::{lexer, Mode, Tok};
use rustpython_parser::{lexer, Mode};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
@@ -168,15 +168,15 @@ fn create_remove_param_fix(locator: &Locator, expr: &Expr, mode_param: &Expr) ->
fix_end = Some(range.end());
break;
}
if delete_first_arg && matches!(tok, Tok::Name { .. }) {
if delete_first_arg && tok.is_name() {
fix_end = Some(range.start());
break;
}
if matches!(tok, Tok::Lpar) {
if tok.is_lpar() {
is_first_arg = true;
fix_start = Some(range.end());
}
if matches!(tok, Tok::Comma) {
if tok.is_comma() {
is_first_arg = false;
if !delete_first_arg {
fix_start = Some(range.start());

View File

@@ -159,18 +159,17 @@ impl AlwaysAutofixableViolation for AmbiguousUnicodeCharacterComment {
}
pub(crate) fn ambiguous_unicode_character(
diagnostics: &mut Vec<Diagnostic>,
locator: &Locator,
range: TextRange,
context: Context,
settings: &Settings,
) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
) {
let text = locator.slice(range);
// Most of the time, we don't need to check for ambiguous unicode characters at all.
if text.is_ascii() {
return diagnostics;
return;
}
// Iterate over the "words" in the text.
@@ -232,8 +231,6 @@ pub(crate) fn ambiguous_unicode_character(
}
word_candidates.clear();
}
diagnostics
}
bitflags! {