Skip to content

Commit aeca20b

Browse files
committed
add fix-its for bare regex literal with leading and/or trailing space
added "convert to extended regex literal" and "insert '\'" to `SwiftSyntax.TokenDiagnostic.fixIts` modified related test cases
1 parent 8b53f92 commit aeca20b

8 files changed

+536
-54
lines changed

Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,50 @@ extension SwiftSyntax.TokenDiagnostic {
325325
changes.append(.replaceLeadingTrivia(token: nextToken, newTrivia: []))
326326
}
327327
return [FixIt(message: .removeExtraneousWhitespace, changes: changes)]
328+
case .spaceAtStartOfRegexLiteral:
329+
var ancestor = Syntax(token)
330+
while ancestor.kind != .regexLiteralExpr, let parent = ancestor.parent {
331+
ancestor = parent
332+
}
333+
334+
let regexLiteral = ancestor.cast(RegexLiteralExprSyntax.self)
335+
let regexText = regexLiteral.regex.text
336+
let lastIndex = regexText.index(before: regexText.endIndex)
337+
let escapedRegexText: String =
338+
if regexText.startIndex != lastIndex && regexText[lastIndex].isWhitespace {
339+
#"\\#(regexText[..<lastIndex])\ "#
340+
} else {
341+
"\\" + regexText
342+
}
343+
344+
return [
345+
FixIt(
346+
message: .convertToExtendedRegexLiteral,
347+
changes: [
348+
.replace(
349+
oldNode: ancestor,
350+
newNode: Syntax(
351+
regexLiteral
352+
.with(\.openingPounds, .poundToken())
353+
.with(\.closingPounds, .poundToken(trailingTrivia: regexLiteral.trailingTrivia))
354+
.with(\.closingSlash.trailingTrivia, [])
355+
)
356+
)
357+
]
358+
),
359+
FixIt(
360+
message: .insertBackslash,
361+
changes: [
362+
.replace(
363+
oldNode: ancestor,
364+
newNode: Syntax(
365+
regexLiteral
366+
.with(\.regex, .regexLiteralPattern(escapedRegexText))
367+
)
368+
)
369+
]
370+
),
371+
]
328372
default:
329373
return []
330374
}

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,9 @@ extension FixItMessage where Self == StaticParserFixIt {
655655
public static var insertAttributeArguments: Self {
656656
.init("insert attribute argument")
657657
}
658+
public static var insertBackslash: Self {
659+
.init("insert '\\'")
660+
}
658661
public static var insertNewline: Self {
659662
.init("insert newline")
660663
}
@@ -691,6 +694,9 @@ extension FixItMessage where Self == StaticParserFixIt {
691694
public static var wrapInBackticks: Self {
692695
.init("if this name is unavoidable, use backticks to escape it")
693696
}
697+
public static var convertToExtendedRegexLiteral: Self {
698+
.init("convert to extended regex literal")
699+
}
694700
}
695701

696702
public struct InsertFixIt: ParserFixIt {

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,25 +1268,34 @@ final class DeclarationTests: ParserTestCase {
12681268
),
12691269
DiagnosticSpec(
12701270
locationMarker: "4️⃣",
1271-
message: "bare slash regex literal may not start with space"
1271+
message: "bare slash regex literal may not start with space",
1272+
fixIts: [
1273+
"convert to extended regex literal",
1274+
#"insert '\'"#,
1275+
]
12721276
),
12731277
DiagnosticSpec(
12741278
locationMarker: "5️⃣",
12751279
message: "expected '/' to end regex literal",
12761280
notes: [NoteSpec(locationMarker: "3️⃣", message: "to match this opening '/'")],
1277-
fixIts: ["insert '/\'"]
1281+
fixIts: ["insert '/'"]
12781282
),
12791283
DiagnosticSpec(
12801284
locationMarker: "6️⃣",
12811285
message: "extraneous brace at top level"
12821286
),
12831287
],
1284-
fixedSource: """
1288+
applyFixIts: [
1289+
"insert '}'",
1290+
#"insert '\'"#,
1291+
"insert '/'",
1292+
],
1293+
fixedSource: #"""
12851294
struct S {
12861295
}
1287-
/ ###line 25 "line-directive.swift"/
1296+
/\ ###line 25 "line-directive.swift"/
12881297
}
1289-
"""
1298+
"""#
12901299
)
12911300
}
12921301

Tests/SwiftParserTest/RegexLiteralTests.swift

Lines changed: 186 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,37 @@ final class RegexLiteralTests: ParserTestCase {
429429
/1️⃣ a/
430430
""",
431431
diagnostics: [
432-
DiagnosticSpec(message: "bare slash regex literal may not start with space")
433-
]
432+
DiagnosticSpec(
433+
message: "bare slash regex literal may not start with space",
434+
fixIts: [
435+
"convert to extended regex literal",
436+
#"insert '\'"#,
437+
]
438+
)
439+
],
440+
applyFixIts: ["convert to extended regex literal"],
441+
fixedSource: """
442+
#/ a/#
443+
"""
444+
)
445+
446+
assertParse(
447+
"""
448+
/1️⃣ a/
449+
""",
450+
diagnostics: [
451+
DiagnosticSpec(
452+
message: "bare slash regex literal may not start with space",
453+
fixIts: [
454+
"convert to extended regex literal",
455+
#"insert '\'"#,
456+
]
457+
)
458+
],
459+
applyFixIts: [#"insert '\'"#],
460+
fixedSource: #"""
461+
/\ a/
462+
"""#
434463
)
435464
}
436465

@@ -440,8 +469,37 @@ final class RegexLiteralTests: ParserTestCase {
440469
let x = /1️⃣ a/
441470
""",
442471
diagnostics: [
443-
DiagnosticSpec(message: "bare slash regex literal may not start with space")
444-
]
472+
DiagnosticSpec(
473+
message: "bare slash regex literal may not start with space",
474+
fixIts: [
475+
"convert to extended regex literal",
476+
#"insert '\'"#,
477+
]
478+
)
479+
],
480+
applyFixIts: ["convert to extended regex literal"],
481+
fixedSource: """
482+
let x = #/ a/#
483+
"""
484+
)
485+
486+
assertParse(
487+
"""
488+
let x = /1️⃣ a/
489+
""",
490+
diagnostics: [
491+
DiagnosticSpec(
492+
message: "bare slash regex literal may not start with space",
493+
fixIts: [
494+
"convert to extended regex literal",
495+
#"insert '\'"#,
496+
]
497+
)
498+
],
499+
applyFixIts: [#"insert '\'"#],
500+
fixedSource: #"""
501+
let x = /\ a/
502+
"""#
445503
)
446504
}
447505

@@ -495,8 +553,37 @@ final class RegexLiteralTests: ParserTestCase {
495553
/1️⃣ /
496554
""",
497555
diagnostics: [
498-
DiagnosticSpec(message: "bare slash regex literal may not start with space")
499-
]
556+
DiagnosticSpec(
557+
message: "bare slash regex literal may not start with space",
558+
fixIts: [
559+
"convert to extended regex literal",
560+
#"insert '\'"#,
561+
]
562+
)
563+
],
564+
applyFixIts: ["convert to extended regex literal"],
565+
fixedSource: """
566+
#/ /#
567+
"""
568+
)
569+
570+
assertParse(
571+
"""
572+
/1️⃣ /
573+
""",
574+
diagnostics: [
575+
DiagnosticSpec(
576+
message: "bare slash regex literal may not start with space",
577+
fixIts: [
578+
"convert to extended regex literal",
579+
#"insert '\'"#,
580+
]
581+
)
582+
],
583+
applyFixIts: [#"insert '\'"#],
584+
fixedSource: #"""
585+
/\ \ /
586+
"""#
500587
)
501588
}
502589

@@ -506,8 +593,37 @@ final class RegexLiteralTests: ParserTestCase {
506593
x += /1️⃣ /
507594
""",
508595
diagnostics: [
509-
DiagnosticSpec(message: "bare slash regex literal may not start with space")
510-
]
596+
DiagnosticSpec(
597+
message: "bare slash regex literal may not start with space",
598+
fixIts: [
599+
"convert to extended regex literal",
600+
#"insert '\'"#,
601+
]
602+
)
603+
],
604+
applyFixIts: ["convert to extended regex literal"],
605+
fixedSource: """
606+
x += #/ /#
607+
"""
608+
)
609+
610+
assertParse(
611+
"""
612+
x += /1️⃣ /
613+
""",
614+
diagnostics: [
615+
DiagnosticSpec(
616+
message: "bare slash regex literal may not start with space",
617+
fixIts: [
618+
"convert to extended regex literal",
619+
#"insert '\'"#,
620+
]
621+
)
622+
],
623+
applyFixIts: [#"insert '\'"#],
624+
fixedSource: #"""
625+
x += /\ \ /
626+
"""#
511627
)
512628
}
513629

@@ -525,8 +641,37 @@ final class RegexLiteralTests: ParserTestCase {
525641
/1️⃣ /
526642
""",
527643
diagnostics: [
528-
DiagnosticSpec(message: "bare slash regex literal may not start with space")
529-
]
644+
DiagnosticSpec(
645+
message: "bare slash regex literal may not start with space",
646+
fixIts: [
647+
"convert to extended regex literal",
648+
#"insert '\'"#,
649+
]
650+
)
651+
],
652+
applyFixIts: ["convert to extended regex literal"],
653+
fixedSource: """
654+
#/ /#
655+
"""
656+
)
657+
658+
assertParse(
659+
"""
660+
/1️⃣ /
661+
""",
662+
diagnostics: [
663+
DiagnosticSpec(
664+
message: "bare slash regex literal may not start with space",
665+
fixIts: [
666+
"convert to extended regex literal",
667+
#"insert '\'"#,
668+
]
669+
)
670+
],
671+
applyFixIts: [#"insert '\'"#],
672+
fixedSource: #"""
673+
/\ /
674+
"""#
530675
)
531676
}
532677

@@ -536,8 +681,37 @@ final class RegexLiteralTests: ParserTestCase {
536681
let x = /1️⃣ /
537682
""",
538683
diagnostics: [
539-
DiagnosticSpec(message: "bare slash regex literal may not start with space")
540-
]
684+
DiagnosticSpec(
685+
message: "bare slash regex literal may not start with space",
686+
fixIts: [
687+
"convert to extended regex literal",
688+
#"insert '\'"#,
689+
]
690+
)
691+
],
692+
applyFixIts: ["convert to extended regex literal"],
693+
fixedSource: """
694+
let x = #/ /#
695+
"""
696+
)
697+
698+
assertParse(
699+
"""
700+
let x = /1️⃣ /
701+
""",
702+
diagnostics: [
703+
DiagnosticSpec(
704+
message: "bare slash regex literal may not start with space",
705+
fixIts: [
706+
"convert to extended regex literal",
707+
#"insert '\'"#,
708+
]
709+
)
710+
],
711+
applyFixIts: [#"insert '\'"#],
712+
fixedSource: #"""
713+
let x = /\ /
714+
"""#
541715
)
542716
}
543717

0 commit comments

Comments
 (0)