Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3505209

Browse files
committed
Add -quickfix compiler option to apply quick fixes to source files
With 2.13.12 the compiler starts providing quick fixes with certainwarnings and errors. Typically these are presented in IDEs, howeverit can also be practical to have the compiler directly patch the sourcefiles.From `-quickfix:help`:```Apply quick fixes provided by the compiler for warnings and errors to source files.Syntax: -quickfix:<filter>,...,<filter><filter> syntax is the same as for configurable warnings, see `-Wconf:help`. Examples: -quickfix:any apply all available quick fixes -quickfix:msg=Auto-application apply quick fixes where the message contains "Auto-application"Use `-Wconf:any:warning-verbose` to display applicable message filters with each warning.```
1 parent98a4234 commit3505209

27 files changed

+323
-72
lines changed

‎src/compiler/scala/tools/nsc/Reporting.scala‎

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ package scala
1414
packagetools
1515
packagensc
1616

17+
importjava.io.IOException
18+
importjava.nio.charset.Charset
19+
importjava.nio.file.{Files,Path,Paths}
1720
importjava.util.regex.PatternSyntaxException
18-
importscala.annotation.nowarn
21+
importscala.annotation.{nowarn,tailrec}
1922
importscala.collection.mutable
2023
importscala.reflect.internal
2124
importscala.reflect.internal.util.StringOps.countElementsAsString
22-
importscala.reflect.internal.util.{CodeAction,NoSourceFile,Position,SourceFile}
25+
importscala.reflect.internal.util.{CodeAction,NoSourceFile,Position,SourceFile,TextEdit}
2326
importscala.tools.nsc.Reporting.Version.{NonParseableVersion,ParseableVersion}
2427
importscala.tools.nsc.Reporting._
2528
importscala.tools.nsc.settings.NoScalaVersion
@@ -62,13 +65,50 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
6265
else conf
6366
}
6467

68+
privatelazyvalquickfixFilters= {
69+
if (settings.quickfix.isSetByUser&& settings.quickfix.value.isEmpty) {
70+
globalError(s"Missing message filter for `-quickfix`; see `-quickfix:help` or use `-quickfix:any` to apply all available quick fixes.")
71+
Nil
72+
}else {
73+
valparsed= settings.quickfix.value.map(WConf.parseFilter(_, rootDirPrefix))
74+
valmsgs= parsed.collect {caseLeft(msg)=> msg }
75+
if (msgs.nonEmpty) {
76+
globalError(s"Failed to parse `-quickfix` filters:${settings.quickfix.value.mkString(",")}\n${msgs.mkString("\n")}")
77+
Nil
78+
}else parsed.collect {caseRight(f)=> f }
79+
}
80+
}
81+
82+
privatevalskipRewriteAction=Set(Action.WarningSummary,Action.InfoSummary,Action.Silent)
83+
84+
privatedefregisterTextEdit(m:Message):Boolean=
85+
if (quickfixFilters.exists(f=> f.matches(m))) {
86+
textEdits.addAll(m.actions.flatMap(_.edits))
87+
true
88+
}
89+
elsefalse
90+
91+
privatedefregisterErrorTextEdit(pos:Position,msg:String,actions:List[CodeAction]):Boolean= {
92+
valmatches= quickfixFilters.exists({
93+
caseMessageFilter.Any=>true
94+
casemp:MessageFilter.MessagePattern=> mp.check(msg)
95+
casesp:MessageFilter.SourcePattern=> sp.check(pos)
96+
case _=>false
97+
})
98+
if (matches)
99+
textEdits.addAll(actions.flatMap(_.edits))
100+
matches
101+
}
102+
65103
privatevalsummarizedWarnings: mutable.Map[WarningCategory, mutable.LinkedHashMap[Position,Message]]= mutable.HashMap.empty
66104
privatevalsummarizedInfos: mutable.Map[WarningCategory, mutable.LinkedHashMap[Position,Message]]= mutable.HashMap.empty
67105

68106
privatevalsuppressions: mutable.LinkedHashMap[SourceFile, mutable.ListBuffer[Suppression]]= mutable.LinkedHashMap.empty
69107
privatevalsuppressionsComplete: mutable.Set[SourceFile]= mutable.Set.empty
70108
privatevalsuspendedMessages: mutable.LinkedHashMap[SourceFile, mutable.LinkedHashSet[Message]]= mutable.LinkedHashMap.empty
71109

110+
privatevaltextEdits: mutable.Set[TextEdit]= mutable.Set.empty
111+
72112
// Used in REPL. The old run is used for parsing. Don't discard its suspended warnings.
73113
definitFrom(old:PerRunReporting):Unit= {
74114
suspendedMessages++= old.suspendedMessages
@@ -100,6 +140,10 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
100140
sups<- suppressions.remove(source)
101141
sup<- sups.reverse
102142
}if (!sup.used&&!sup.synthetic) issueWarning(Message.Plain(sup.annotPos,"@nowarn annotation does not suppress any warnings",WarningCategory.UnusedNowarn,"",Nil))
143+
144+
// apply quick fixes
145+
quickfix(textEdits)
146+
textEdits.clear()
103147
}
104148

105149
defreportSuspendedMessages(unit:CompilationUnit):Unit= {
@@ -119,6 +163,14 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
119163
}
120164

121165
privatedefissueWarning(warning:Message):Unit= {
166+
valaction= wconf.action(warning)
167+
168+
valquickfixed= {
169+
if (!skipRewriteAction(action)&& registerTextEdit(warning))s"[rewritten by -quickfix]${warning.msg}"
170+
elseif (warning.actions.exists(_.edits.nonEmpty))s"[quick fix available]${warning.msg}"
171+
else warning.msg
172+
}
173+
122174
defifNonEmpty(kind:String,filter:String)=if (filter.nonEmpty)s",$kind=$filter"else""
123175
deffilterHelp=
124176
s"msg=<part of the message>, cat=${warning.category.name}"+
@@ -133,12 +185,13 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
133185
"\nScala 3 migration messages are errors under -Xsource:3. Use -Wconf / @nowarn to filter them or add -Xmigration to demote them to warnings."
134186
else""
135187
defhelpMsg(kind:String,isError:Boolean=false)=
136-
s"${warning.msg}${scala3migration(isError)}\nApplicable -Wconf / @nowarn filters for this$kind:$filterHelp"
137-
wconf.action(warning)match {
188+
s"$quickfixed${scala3migration(isError)}\nApplicable -Wconf / @nowarn filters for this$kind:$filterHelp"
189+
190+
actionmatch {
138191
caseAction.Error=> reporter.error(warning.pos, helpMsg("fatal warning", isError=true), warning.actions)
139-
caseAction.Warning=> reporter.warning(warning.pos,warning.msg, warning.actions)
192+
caseAction.Warning=> reporter.warning(warning.pos,quickfixed, warning.actions)
140193
caseAction.WarningVerbose=> reporter.warning(warning.pos, helpMsg("warning"), warning.actions)
141-
caseAction.Info=> reporter.echo(warning.pos,warning.msg, warning.actions)
194+
caseAction.Info=> reporter.echo(warning.pos,quickfixed, warning.actions)
142195
caseAction.InfoVerbose=> reporter.echo(warning.pos, helpMsg("message"), warning.actions)
143196
case a@ (Action.WarningSummary|Action.InfoSummary)=>
144197
valm= summaryMap(a, warning.category.summaryCategory)
@@ -299,6 +352,16 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
299352
defwarning(pos:Position,msg:String,category:WarningCategory,site:Symbol,origin:String):Unit=
300353
issueIfNotSuppressed(Message.Origin(pos, msg, category, siteName(site), origin, actions=Nil))
301354

355+
// Remember CodeActions that match `-quickfix` and report the error through the reporter
356+
deferror(pos:Position,msg:String,actions:List[CodeAction]):Unit= {
357+
valquickfixed= {
358+
if (registerErrorTextEdit(pos, msg, actions))s"[rewritten by -quickfix]$msg"
359+
elseif (actions.exists(_.edits.nonEmpty))s"[quick fix available]$msg"
360+
else msg
361+
}
362+
reporter.error(pos, quickfixed, actions)
363+
}
364+
302365
// used by Global.deprecationWarnings, which is used by sbt
303366
defdeprecationWarnings:List[(Position,String)]= summaryMap(Action.WarningSummary,WarningCategory.Deprecation).toList.map(p=> (p._1, p._2.msg))
304367
defuncheckedWarnings:List[(Position,String)]= summaryMap(Action.WarningSummary,WarningCategory.Unchecked).toList.map(p=> (p._1, p._2.msg))
@@ -330,6 +393,91 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
330393
if (settings.fatalWarnings.value&& reporter.hasWarnings)
331394
reporter.error(NoPosition,"No warnings can be incurred under -Werror.")
332395
}
396+
397+
privateobjectquickfix {
398+
/** Source code at a position. Either a line with caret (offset), else the code at the range position.*/
399+
defcodeOf(pos:Position,source:SourceFile):String=
400+
if (pos.start< pos.end)newString(source.content.slice(pos.start, pos.end))
401+
else {
402+
valline= source.offsetToLine(pos.point)
403+
valcode= source.lines(line).next()
404+
valcaret=""* (pos.point- source.lineToOffset(line))+"^"
405+
s"$code\n$caret"
406+
}
407+
408+
409+
defcheckNoOverlap(patches:List[TextEdit],source:SourceFile):Boolean= {
410+
varok=true
411+
for (List(p1, p2)<- patches.sliding(2)if p1.position.end> p2.position.start) {
412+
ok=false
413+
valmsg=
414+
s"""overlapping quick fixes in${source.file.file.getAbsolutePath}:
415+
|
416+
|add `${p1.newText}` at
417+
|${codeOf(p1.position, source)}
418+
|
419+
|add `${p2.newText}` at
420+
|${codeOf(p2.position, source)}""".stripMargin.trim
421+
issueWarning(Message.Plain(p1.position, msg,WarningCategory.Other,"",Nil))
422+
}
423+
ok
424+
}
425+
426+
defunderlyingFile(source:SourceFile):Option[Path]= {
427+
valfileClass= source.file.getClass.getName
428+
valp=if (fileClass.endsWith("xsbt.ZincVirtualFile")) {
429+
importscala.language.reflectiveCalls
430+
valpath= source.file.asInstanceOf[ {defunderlying(): {defid():String}}].underlying().id()
431+
Some(Paths.get(path))
432+
}else
433+
Option(source.file.file).map(_.toPath)
434+
valr= p.filter(Files.exists(_))
435+
if (r.isEmpty)
436+
issueWarning(Message.Plain(NoPosition,s"Failed to apply quick fixes, file does not exist:${source.file}",WarningCategory.Other,"",Nil))
437+
r
438+
}
439+
440+
valencoding=Charset.forName(settings.encoding.value)
441+
442+
definsertEdits(sourceChars:Array[Char],edits:List[TextEdit],file:Path):Array[Byte]= {
443+
valpatchedChars=newArray[Char](sourceChars.length+ edits.iterator.map(_.delta).sum)
444+
@tailrecdefloop(edits:List[TextEdit],inIdx:Int,outIdx:Int):Unit= {
445+
defcopy(upTo:Int):Int= {
446+
valuntouched= upTo- inIdx
447+
System.arraycopy(sourceChars, inIdx, patchedChars, outIdx, untouched)
448+
outIdx+ untouched
449+
}
450+
editsmatch {
451+
case e:: es=>
452+
valoutNew= copy(e.position.start)
453+
e.newText.copyToArray(patchedChars, outNew)
454+
loop(es, e.position.end, outNew+ e.newText.length)
455+
case _=>
456+
valoutNew= copy(sourceChars.length)
457+
if (outNew!= patchedChars.length)
458+
issueWarning(Message.Plain(NoPosition,s"Unexpected content length when applying quick fixes; verify the changes to${file.toFile.getAbsolutePath}",WarningCategory.Other,"",Nil))
459+
}
460+
}
461+
462+
loop(edits,0,0)
463+
newString(patchedChars).getBytes(encoding)
464+
}
465+
466+
defapply(edits: mutable.Set[TextEdit]):Unit= {
467+
for ((source, edits)<- edits.groupBy(_.position.source).view.mapValues(_.toList.sortBy(_.position.start))) {
468+
if (checkNoOverlap(edits, source)) {
469+
underlyingFile(source) foreach { file=>
470+
valsourceChars=newString(Files.readAllBytes(file), encoding).toCharArray
471+
tryFiles.write(file, insertEdits(sourceChars, edits, file))
472+
catch {
473+
casee:IOException=>
474+
issueWarning(Message.Plain(NoPosition,s"Failed to apply quick fixes to${file.toFile.getAbsolutePath}\n${e.getMessage}",WarningCategory.Other,"",Nil))
475+
}
476+
}
477+
}
478+
}
479+
}
480+
}
333481
}
334482
}
335483

@@ -532,7 +680,8 @@ object Reporting {
532680
}
533681

534682
finalcaseclassMessagePattern(pattern:Regex)extendsMessageFilter {
535-
defmatches(message:Message):Boolean= pattern.findFirstIn(message.msg).nonEmpty
683+
defcheck(msg:String)= pattern.findFirstIn(msg).nonEmpty
684+
defmatches(message:Message):Boolean= check(message.msg)
536685
}
537686

538687
finalcaseclassSitePattern(pattern:Regex)extendsMessageFilter {
@@ -542,10 +691,11 @@ object Reporting {
542691
finalcaseclassSourcePattern(pattern:Regex)extendsMessageFilter {
543692
private[this]valcache= mutable.Map.empty[SourceFile,Boolean]
544693

545-
defmatches(message:Message):Boolean= cache.getOrElseUpdate(message.pos.source, {
546-
valsourcePath=message.pos.source.file.canonicalPath.replace("\\","/")
694+
defcheck(pos:Position)= cache.getOrElseUpdate(pos.source, {
695+
valsourcePath= pos.source.file.canonicalPath.replace("\\","/")
547696
pattern.findFirstIn(sourcePath).nonEmpty
548697
})
698+
defmatches(message:Message):Boolean= check(message.pos)
549699
}
550700

551701
finalcaseclassDeprecatedOrigin(pattern:Regex)extendsMessageFilter {

‎src/compiler/scala/tools/nsc/ast/parser/Parsers.scala‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ self =>
251251
valsyntaxErrors=newListBuffer[(Int,String,List[CodeAction])]
252252
defshowSyntaxErrors()=
253253
for ((offset, msg, actions)<- syntaxErrors)
254-
reporter.error(o2p(offset), msg, actions)
254+
runReporting.error(o2p(offset), msg, actions)
255255

256256
overridedefsyntaxError(offset:Offset,msg:String,actions:List[CodeAction]):Unit= {
257257
if (smartParsing) syntaxErrors+= ((offset, msg, actions))
258-
elsereporter.error(o2p(offset), msg, actions)
258+
elserunReporting.error(o2p(offset), msg, actions)
259259
}
260260

261261
overridedefincompleteInputError(msg:String,actions:List[CodeAction]):Unit= {

‎src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@ trait StandardScalaSettings { _: MutableSettings =>
5151
valnowarn=BooleanSetting ("-nowarn","Generate no warnings.") withAbbreviation"--no-warnings" withPostSetHook { s=>if (s.value) maxwarns.value=0 }
5252
valoptimise:BooleanSetting// depends on post hook which mutates other settings
5353
valprint=BooleanSetting ("-print","Print program with Scala-specific features removed.") withAbbreviation"--print"
54+
valquickfix=MultiStringSetting(
55+
"-quickfix",
56+
"filters",
57+
"Apply quick fixes provided by the compiler for warnings and errors to source files",
58+
helpText=Some(
59+
"""Apply quick fixes provided by the compiler for warnings and errors to source files.
60+
|Syntax: -quickfix:<filter>,...,<filter>
61+
|
62+
|<filter> syntax is the same as for configurable warnings, see `-Wconf:help`. Examples:
63+
| -quickfix:any apply all available quick fixes
64+
| -quickfix:msg=Auto-application apply quick fixes where the message contains "Auto-application"
65+
|
66+
|Use `-Wconf:any:warning-verbose` to display applicable message filters with each warning.
67+
|""".stripMargin),
68+
prepend=true)
5469
valrelease=
5570
ChoiceSetting("-release","release","Compile for a version of the Java API and target class file.",AllTargetVersions, normalizeTarget(javaSpecVersion))
5671
.withPostSetHook { setting=>

‎src/reflect/scala/reflect/internal/util/CodeAction.scala‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ case class CodeAction(title: String, description: Option[String], edits: List[Te
3636
*@groupname Common Commonly used methods
3737
*@group ReflectionAPI
3838
*/
39-
caseclassTextEdit(position:Position,newText:String)
39+
caseclassTextEdit(position:Position,newText:String) {
40+
defdelta:Int= newText.length- (position.end- position.start)
41+
}

‎test/files/neg/auto-application.check‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ auto-application.scala:5: error: Int does not take parameters
77
auto-application.scala:6: error: Int does not take parameters
88
("": Object).##()
99
^
10-
auto-application.scala:9: warning: Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method meth,
10+
auto-application.scala:9: warning:[quick fix available]Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method meth,
1111
or remove the empty argument list from its definition (Java-defined methods are exempt).
1212
In Scala 3, an unapplied method like this will be eta-expanded into a function.
1313
meth // warn, auto-application (of nilary methods) is deprecated

‎test/files/neg/for-comprehension-old.check‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
for-comprehension-old.scala:6: error: `val` keyword in for comprehension is unsupported: just remove `val`
1+
for-comprehension-old.scala:6: error:[quick fix available]`val` keyword in for comprehension is unsupported: just remove `val`
22
for (val x <- 1 to 5 ; y = x) yield x+y // fail
33
^
4-
for-comprehension-old.scala:7: error: `val` keyword in for comprehension is unsupported: just remove `val`
4+
for-comprehension-old.scala:7: error:[quick fix available]`val` keyword in for comprehension is unsupported: just remove `val`
55
for (val x <- 1 to 5 ; val y = x) yield x+y // fail
66
^
7-
for-comprehension-old.scala:11: error: `val` keyword in for comprehension is unsupported: just remove `val`
7+
for-comprehension-old.scala:11: error:[quick fix available]`val` keyword in for comprehension is unsupported: just remove `val`
88
for (z <- 1 to 2 ; val x <- 1 to 5 ; y = x) yield x+y // fail
99
^
10-
for-comprehension-old.scala:12: error: `val` keyword in for comprehension is unsupported: just remove `val`
10+
for-comprehension-old.scala:12: error:[quick fix available]`val` keyword in for comprehension is unsupported: just remove `val`
1111
for (z <- 1 to 2 ; val x <- 1 to 5 ; val y = x) yield x+y // fail
1212
^
13-
for-comprehension-old.scala:5: warning: `val` keyword in for comprehension is deprecated: instead, bind the value without `val`
13+
for-comprehension-old.scala:5: warning:[quick fix available]`val` keyword in for comprehension is deprecated: instead, bind the value without `val`
1414
for (x <- 1 to 5 ; val y = x) yield x+y // fail
1515
^
16-
for-comprehension-old.scala:7: warning: `val` keyword in for comprehension is deprecated: instead, bind the value without `val`
16+
for-comprehension-old.scala:7: warning:[quick fix available]`val` keyword in for comprehension is deprecated: instead, bind the value without `val`
1717
for (val x <- 1 to 5 ; val y = x) yield x+y // fail
1818
^
19-
for-comprehension-old.scala:10: warning: `val` keyword in for comprehension is deprecated: instead, bind the value without `val`
19+
for-comprehension-old.scala:10: warning:[quick fix available]`val` keyword in for comprehension is deprecated: instead, bind the value without `val`
2020
for (z <- 1 to 2 ; x <- 1 to 5 ; val y = x) yield x+y // fail
2121
^
22-
for-comprehension-old.scala:12: warning: `val` keyword in for comprehension is deprecated: instead, bind the value without `val`
22+
for-comprehension-old.scala:12: warning:[quick fix available]`val` keyword in for comprehension is deprecated: instead, bind the value without `val`
2323
for (z <- 1 to 2 ; val x <- 1 to 5 ; val y = x) yield x+y // fail
2424
^
2525
4 warnings

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp