SwiftGen / StencilSwiftKit

A framework bringing additional nodes & filters to Stencil dedicated to Swift code generation
MIT License
294 stars 56 forks source link

Identifiers starting with numbers when combining `swiftIdentifier` and `snakeToCamelCase` #30

Closed dDomovoj closed 7 years ago

dDomovoj commented 7 years ago

Generated output example: ...enum UniversLTStd: String, FontConvertible { case 65Bold = "UniversLTStd-Bold" case 67BoldCondensed = "UniversLTStd-BoldCn" case 57Condensed = "UniversLTStd-Cn" case 47LightCondensed = "UniversLTStd-LightCn" case 47LightCondensedOblique = "UniversLTStd-LightCnObl" case 59UltraCondensed = "UniversLTStd-UltraCn" }...

djbe commented 7 years ago

This is a duplicate of https://github.com/SwiftGen/SwiftGen/issues/289. While it is the right place for this issue, most of the discussion already happened there.

djbe commented 7 years ago

I'm reopening this, as it's still not fixed, and we should discuss what the best solution would be.

djbe commented 7 years ago

Given an input of "25 Ultra Light" and depending on the order, we get:

djbe commented 7 years ago

Should we just use the 2nd case (don't strip leading underscores)? Or modify the function to accept an enum:

I think we'll probably use the keepOne everywhere with maybe 1 or 2 exceptions.

AliSoftware commented 7 years ago

From a logical standpoint, I think we should apply swiftIdentifier last, because we want to apply filters to make it look nice / the way we prefer first, and end the list of filters with "now that we have what we hoped, make just sure it's gonna be valid in the end". While applying swiftIdentifier first then adding more transforms after that doesn't guarantee that the final result after all the transforms are still gonna be valid Swift.

What I don't understand is why, once transformed "25 Ultra Light" to CamelCase using snakeToCamelCase, we don't end up with 25UltraLight — which should at last be transformed into valid swift identifier _25UltraLight at the end. Should we improve the snakeToCamelCase filter?

AliSoftware commented 7 years ago

Side note: we should add some examples containing spaces in that filter's documentation to make it clear of its behaviour with spaces. I think that's actually the core of the issue (and imho snakeToCameCase should just remove the spaces and capitalise the letter after said space)

djbe commented 7 years ago

Not sure about that though, we'd lose information if the name was "25 ultra light".

djbe commented 7 years ago

And, swiftIdentifier uppercases the first letter --> we still need to apply lowerFirstWord

AliSoftware commented 7 years ago

Not sure about that though, we'd lose information if the name was "25 ultra light".

How so? "25 ultra light"|snakeToCamelCase should still result in "25UltraLight". Which would then result in "_25UltraLight" once we apply swiftIdentifier to that result.

djbe commented 7 years ago

snakeCaseToCamelCase only thinks in terms of underscores, it doesn't (and shouldn't) care about other characters.

      if try containsAnyLowercasedChar(string) {
        let comps = string.components(separatedBy: "_")
        unprefixed = comps.map { titlecase($0) }.joined(separator: "")
      }
AliSoftware commented 7 years ago

Currently, the code does this which doesn't take spaces into account. So "25 Ultra Light" is unchanged under snakeToCamelCase. That's the main problem, as imho it should replace spaces with underscores first. Or we should apply a filter that does that first.

AliSoftware commented 7 years ago

Ok, so let's apply {{font.name|replace:" ","_"|snakeToCamelCase|swiftIdentifier|lowerFirstWord then.

djbe commented 7 years ago

Well, that's why we have swiftidentifier.

Maybe we should swiftIdentifier|snakeToCamelCase:true|swiftIdentifier.

djbe commented 7 years ago

I woulnd't go with the replace filter, it's too limited for future swift changes. What do we do with other invalid characters?

AliSoftware commented 7 years ago

No, swiftIdentifier does more than replacing spaces with underscores. It also replaces other invalid swift characters. Applying the filter twice seems odd to me logically…

Let's take more convoluted examples to look at what could be logically right

What about "12 @ 34 % 56 + 78 Hello world", I think we want to get "_12_34_56_78_Hello_world" as a result? Because spaces should not be turned to underscores by snakeToCameCalse? Or maybe we would prefer "_123456___78_Hello_world"? If so, for "25 Ultra Light" we would want "_25_UltraLight" to be consistent (invalid chars kept as "")?

Or would we want "12 @ 34 % 56 + 78 Hello world" to result to "_12345678HelloWorld" ? Or to "_12_34_56_78HelloWorld" (but what would be the rule of removing underscores generated from invalid chars vs one from spaces vs ones that would avoid numbers next to each others be glued together?)

djbe commented 7 years ago

Right, no idea what's the ideal solution. personally as a end result I'd expect "_25UltraLight". For those other examples, not a clue.

Template writer shouldn't have to worry about all these edge cases, I'm starting to think the swiftIdentifier (or another) filter should do the whole thing? Even the lowerFirstWord part. For swift2 we can just add upperFirstLetter where needed.

Edit: such a filter could even accept an (optional) swift version.

AliSoftware commented 7 years ago

swiftIdentifier shouldn't lower the first word.

It's definitely not the role of swiftIdentifier to do the lowerFirstLetter. But for the other parts, especially avoiding to always chain all those other filters in all the places we use them in the templates, I agree that a convenience filter with options would be nicer to use, even if in the end it's only just a convenience filter applying multiple existing filters in sequence.

Maybe the rule should be: (1) replace " [a-z]" (space followed by lowercase letter) with "[A-Z]" (remove space and uppercase first letter), then just apply the current version of swiftIdentifier on the result.

So in practice, it could make sense to make snakeToCamelCase also (optionally, we can add more parameters for that filter if needed) not only replace "" but also replace " " with { "" + uppercase next letter }, and only if the next letter is "capitalizable" (so numbers or symbols which are unchanged when capitalized should keep the "" or " " before them instead of remove them?)

djbe commented 7 years ago

item 1: we also have a lowerFirstLetter now 😄

Should that convenience filter also apply the snakeToCamelCase filter? Something like what you had before, then: replace:" ","_"|snakeToCamelCase|swiftIdentifier

We could add this as a variation of the swiftIdentifier behind a parameter. If true, prettify the string, otherwise (default) only the current behaviour.

AliSoftware commented 7 years ago

The more I think about it, the more I think making the swiftIdentifier accepting a parameter like :none/:normal/:false (default) vs :pretty would make sense.

Making that parameter a string describing the "mode" (e.g. pretty) will allow us to provide multiple prettification / convention variants in the (near?) future. Like one that just removes spaces and uppercase the letter after the removed space, but keep original underscores unchanged, another which removes all spaces AND underscores and uppercase the letter after, another which could have even more complex rules in the future to deal differently with existing underscores vs spaces vs special chars, etc.

The first variant that we'd want to implement will probably be equivalent to the current replace:" ","_"|snakeToCamelCase|swiftIdentifier indeed (so like you suggested, just maybe use "true" as a parameter but some other string like "pretty" or "all" or whatnot.


NB: In any case, I don't think it would be the job of the swiftIdentifier filter, whatever its prettification variant, to lowercase the first letter. That should still be left for the template writer to call lowerFirstWord or lowerFirstLetter or upperFirstLetter or whatever they need depending on the context (swift version + type or instance name + other stuff, like maybe for strings people want the string portion unchanged so they can copy/paste keys from their Localizable.strings and use them as is, etc)