Skip to content
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

Merged
merged 9 commits into from
Jan 31, 2017
Merged

Conversation

ysusuk
Copy link
Collaborator

@ysusuk ysusuk commented Jan 29, 2017

…lation.

@olafurpg
Copy link
Member

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.

@ysusuk
Copy link
Collaborator Author

ysusuk commented Jan 29, 2017

Thank you @olafurpg!

@ysusuk
Copy link
Collaborator Author

ysusuk commented Jan 29, 2017

can you suggest me a way to find out what is going after closing brace in s"${foo}bar". i tried

case t @ Term.Name(name) =>
  val closeBrace = nextToken(t.tokens.head)
  val afterCloseBrace = nextToken(closeBrace)

but it doesn't seems to work.

@olafurpg
Copy link
Member

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))

@olafurpg
Copy link
Member

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(), "") =>
Copy link
Member

@olafurpg olafurpg Jan 30, 2017

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"

Copy link
Collaborator Author

@ysusuk ysusuk Jan 30, 2017

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.

Copy link
Member

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 ""

Copy link
Member

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}"
Copy link
Member

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" }

Copy link
Member

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.

Copy link
Collaborator Author

@ysusuk ysusuk Jan 30, 2017

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} "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this expected output?

Copy link
Collaborator Author

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}-"
Copy link
Member

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.

Copy link
Collaborator Author

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"

Copy link
Collaborator Author

@ysusuk ysusuk Jan 30, 2017

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?

Copy link
Member

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.

@ysusuk
Copy link
Collaborator Author

ysusuk commented Jan 30, 2017

Thanx a lot for support @olafurpg! i corrected logic and cleaned up tests!

@codecov-io
Copy link

codecov-io commented Jan 30, 2017

Codecov Report

❗ No coverage uploaded for pull request base (master@abf8ba2).


Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update abf8ba2...0c8173f. Read the comment docs.

Copy link
Member

@olafurpg olafurpg left a 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
Copy link
Member

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)) =>
Copy link
Member

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) =>

Copy link
Collaborator Author

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 {
Copy link
Member

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"
Copy link
Member

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}æ"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added, thank you!

Copy link
Member

@olafurpg olafurpg left a 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) =>
Copy link
Member

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)
Copy link
Member

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.

@olafurpg
Copy link
Member

Yay \o/ thanks for going through all the comments.

I will try to get a release out tomorrow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants