-
Notifications
You must be signed in to change notification settings - Fork 278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#690: Support check for "redundant enclosing brace" in string interpo... #703
Conversation
Thank you for this contribution! Great work. I think there's a missing test case s"${foo}bar". Removing the curly brace will result in the ast to change. |
Thank you @olafurpg! |
can you suggest me a way to find out what is going after closing brace in case t @ Term.Name(name) =>
val closeBrace = nextToken(t.tokens.head)
val afterCloseBrace = nextToken(closeBrace) but it doesn't seems to work. |
Maybe something like this? @ val t = q""" q"$${foo}bar$$banana ok" """; t.parts.tail.zip(t.args)
t: Term.Interpolate = q"$foobar$banana ok"
res9_1: collection.immutable.Seq[(Lit, Term)] = List(("bar", foo), (" ok", banana)) |
Note that there are Interpolation.Splice{End,Start} tokens that you may bump into @ q""" q"$${foo}bar$$banana ok" """.tokens.map(x => x.getClass -> x.structure)
res12: collection.immutable.IndexedSeq[(Class[?0], String) forSome { type ?0 <: Token }] = Vector(
(class scala.meta.tokens.Token$BOF,BOF [0..0)),
(class scala.meta.tokens.Token$Interpolation$Id,q [0..1)),
(class scala.meta.tokens.Token$Interpolation$Start," [1..2)),
(class scala.meta.tokens.Token$Interpolation$Part, [2..2)),
(class scala.meta.tokens.Token$Interpolation$SpliceStart,$ [2..3)),
(class scala.meta.tokens.Token$Ident,foobar [3..9)),
(class scala.meta.tokens.Token$Interpolation$SpliceEnd, [9..9)),
(class scala.meta.tokens.Token$Interpolation$Part, [9..9)),
(class scala.meta.tokens.Token$Interpolation$SpliceStart,$ [9..10)),
(class scala.meta.tokens.Token$Ident,banana [10..16)),
(class scala.meta.tokens.Token$Interpolation$SpliceEnd, [16..16)),
(class scala.meta.tokens.Token$Interpolation$Part, ok [16..19)),
(class scala.meta.tokens.Token$Interpolation$End," [19..20)),
(class scala.meta.tokens.Token$EOF,EOF [20..20))
) |
val openBrace = prevToken(arg.tokens.head) | ||
val closeBrace = nextToken(arg.tokens.head) | ||
(openBrace, closeBrace, value) match { | ||
case (LeftBrace(), RightBrace(), "") => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ""
? Have you tested cases like?
q""
q"$a"
q"$a$b"
q" $a "
q"$a-"
q"${a}1"
q"${a}_2"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @olafurpg. I added all the test cases you mentioned. I'm just not sure that it does make big sense to test all the cases without braces! Since there is a check that args are inside braces! maybe one test here is enough and then anybody that will make a review will not get lost between them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, testing without braces is not so interesting I agree. What if those examples are expected outputs and the inputs have variables wrapped in braces? I still don't understand why it should only rewrite when value is ""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect the current implementation misses some opportunities to rewrite
} | ||
<<< enclosing braces in str interpolation | ||
object a { | ||
def b(d: Int) = s"Hello ${d.toString}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the def b(d: Int) =
part is necessary. Maybe a block is sufficient
{ s"$a" }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the test doesn't have to type check, only parse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it doesn't work without object
. max what i achieved is
<<< redundant enclosing braces in str interpolation
object a {
s"Hello ${value}"
}
>>>
object a {
s"Hello $value"
}
} | ||
>>> | ||
object c { | ||
s" $a " | ||
s" ${a} " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this expected output?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a bug)
} | ||
>>> | ||
object c { | ||
s"${a}-" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, a can be unwrapped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and this one fails s"${a} a${b}b"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i was generally thinking about checks like value.isEmpty
or starts with \n, \r or anything like this! what else should it be? minus/plus... is there kind a list of them? or do you think there is a better approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can look at the spec https://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html#identifiers
Seems like this rewrite should not take place when the right hand part starts with underscore or an alphanumeric character. In other cases, you can safely remove the curly brace.
Thanx a lot for support @olafurpg! i corrected logic and cleaned up tests! |
Codecov Report
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost ready! :)
@@ -49,10 +46,27 @@ object RedundantBraces extends Rewrite { | |||
bodyIsNotTooBig | |||
} | |||
|
|||
val ALPHA_NUMERIC_AND_UNDERSCORE = ((('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9')) :+ '_').toSet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about variables like æ1
?
res0: Boolean = true
@ Character.isLetterOrDigit('-')
res1: Boolean = false
@ Character.isLetterOrDigit('æ')
res3: Boolean = true
val builder = Seq.newBuilder[Patch] | ||
code.collect { | ||
case t: Term.Interpolate if settings.stringInterpolation => | ||
t.parts.tail.zip(t.args).collect { | ||
case (Lit(value: String), arg @ Term.Name(name)) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def isIdentifierStart(value: String) = value.isEmpty || Character.isLetterOrDigit(value.head) || value.head == '_'
...
case xxx if !isIdentifierStart(value) =>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is more like this
def isIdentifierStart(value: String) = value.nonEmpty && (Character.isLetterOrDigit(value.head) || value.head == '_')
when value is empty we don't want to skip patch
case (Lit(value: String), arg @ Term.Name(name)) => | ||
val openBrace = prevToken(arg.tokens.head) | ||
val closeBrace = nextToken(arg.tokens.head) | ||
(openBrace, closeBrace, value.headOption) match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove , value.headOption
} | ||
<<< not redundant enclosing braces in str interpolation (right part starts with underscore) | ||
object a { | ||
s"Hello ${foo}_bar" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also test s"${foo}æ"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added, thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍 just minor nitpick remaining (I hope you don't mind my endless comments!)
builder += Patch(openBrace, closeBrace, name) | ||
case (LeftBrace(), RightBrace(), None) => | ||
(openBrace, closeBrace) match { | ||
case (LeftBrace(), RightBrace()) if !isIdentifierAtStart(value) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we move the !isIdentifierAtStart(value)
guard to the case Lit(value...
line?
@@ -192,6 +192,14 @@ object a { | |||
object a { | |||
s"Hello ${foo}_bar" | |||
} | |||
<<< not redundant enclosing braces in str interpolation (right part starts with special symbol) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move all of the "in str interpolation" tests into a separate file named RedundantBracesStrInterpolation.stat
? Also maybe remove the "redundant braces" part since it's implicit for that test suite, I prefer to keep test names as short as possible.
Yay \o/ thanks for going through all the comments. I will try to get a release out tomorrow |
…lation.