-
Notifications
You must be signed in to change notification settings - Fork 440
Support for comments modifiers #385
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
Conversation
adc3491
to
8ae73f4
Compare
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.
This is looking good overall. I added a few comments inline (it looks worse on first sight than it really is 🙈). Could you also add a few test cases for the new functionality?
} | ||
|
||
public func withLineComment(_ text: String) -> Self { | ||
let piece = TriviaPiece.lineComment("// \(text)") |
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.
This will behave surprisingly if text
contains a newline because the second line will not have a //
. I think we should insert //
in front of every line. As an added bonus, you could make sure that these lines have the same indentation as the current one, by taking a look at the leading trivia after the last .newline
in the node’s current leadingTrivia
.
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.
Working on this one 🤝
9302de1
to
7e2f6bc
Compare
|
||
if let itemLeadingTrivia = itemLeadingTrivia | ||
{ | ||
let formattedLeadingTrivia = [itemLeadingTrivia, format._makeIndent()].reduce(.zero, +) |
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.
Thanks, @ahoppen for pointing me out to
If you now stick that ImportDecl into a SourceFile and add a comment to the SourceFile
I've solved it by doing the following, what do you think?،
also, this should support comments indiation
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 think this will solve the indentation problem but not your problem with dropping IMPORTANT COMMENT
as you described in the PR description.
I would try one of the following (haven’t tested myself):
if let itemLeadingTrivia = itemLeadingTrivia {
leadingTrivia = itemLeadingTrivia + leadingTrivia // Alternative 1
leadingTrivia = format._makeIndent() + itemLeadingTrivia + leadingTrivia // Alternative 2
}
let result = build${type.base_name()}(format: format, leadingTrivia: leadingTrivia)
and dropping the withLeadingTrivia
down here.
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.
actually, I think it solved the dropping of the first Comment In SourceFile {}
, for example:
the output is:
// IMPORTANT COMMENT
import Foundation
// lineComment
// hello
/* This's the new block comment
with new lines support */
import UIKit
class SomeViewController{
/** This's the first nested comment */
let tableView: UITableView
class SomeViewController{
// this's a also a nested comment
let mainNode: SCNNode
}
}
for the following SourceFile
SourceFile {
ImportDecl(path: "Foundation")
.lineComment("IMPORTANT COMMENT")
ImportDecl(path: "UIKit")
.lineComment(
"""
lineComment
hello
"""
)
.blockComment("This's the new block comment\nwith new lines support")
ClassDecl(classOrActorKeyword: .class, identifier: "SomeViewController", membersBuilder: {
VariableDecl(.let, name: "tableView", type: "UITableView")
.docBlockComment("This's the first nested comment")
ClassDecl(classOrActorKeyword: .class,
identifier: "SomeViewController",
membersBuilder: {
VariableDecl(.let, name: "mainNode", type: "SCNNode")
.lineComment("this's a also a nested comment")
})
})
}//.lineComment("This comment will override important comment if placed")
(if a comment on SourceFile is placed, it will override the one on the first item, should we handle it in a special way, which adds both of the comments instead of overriding) ?
is the current output enough for our needs?
please let me know if I'm missing something, also I will check your snippets and see how would they differ :-)
Thank you for the review and for helping me.
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.
(if a comment on SourceFile is placed, it will override the one on the first item, should we handle it in a special way, which adds both of the comments instead of overriding) ?
Yes, definitely. I don’t think we should be dropping comments if the user explicitly asked for them.
Could you add this test case to Tests/SwiftSyntaxBuilderTest?
if let itemLeadingTrivia = itemLeadingTrivia | ||
{ |
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.
Nitpick
if let itemLeadingTrivia = itemLeadingTrivia | |
{ | |
if let itemLeadingTrivia = itemLeadingTrivia { |
|
||
if let itemLeadingTrivia = itemLeadingTrivia | ||
{ | ||
let formattedLeadingTrivia = [itemLeadingTrivia, format._makeIndent()].reduce(.zero, +) |
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 think this will solve the indentation problem but not your problem with dropping IMPORTANT COMMENT
as you described in the PR description.
I would try one of the following (haven’t tested myself):
if let itemLeadingTrivia = itemLeadingTrivia {
leadingTrivia = itemLeadingTrivia + leadingTrivia // Alternative 1
leadingTrivia = format._makeIndent() + itemLeadingTrivia + leadingTrivia // Alternative 2
}
let result = build${type.base_name()}(format: format, leadingTrivia: leadingTrivia)
and dropping the withLeadingTrivia
down here.
7e2f6bc
to
bc6c721
Compare
635b925
to
b665b8e
Compare
I added some tests related to comments behaviors. |
I've added 11 test cases, which handles most of the cases: 4 Test Cases for comments types
2 Tests for multiple line Support
3 Tests for SoureFile Comments
2 Indentation Tests
Thank you for encouraging me to write tests, it helped me discover more cases that weren't handled. |
% if type.buildable() != 'SourceFile': | ||
let result = build${type.base_name()}(format: format, leadingTrivia: leadingTrivia) | ||
% end | ||
% if type.buildable() == 'SourceFile': | ||
let indentedLines = itemLeadingTriviaPieces | ||
.map { Trivia(pieces: [$0, .newlines(1)]) } | ||
.reduce(.zero, +) | ||
|
||
let combinedTrivia = [format._makeIndent(), | ||
leadingTrivia, | ||
indentedLines] | ||
.compactMap { $0 } | ||
.reduce(.zero, +) | ||
% if type.buildable() == 'SourceFile': | ||
let result = buildSourceFile(format: format, leadingTrivia: combinedTrivia) | ||
% end | ||
% else: | ||
|
||
if !itemLeadingTriviaPieces.isEmpty { | ||
let indentedLeadingTrivia = itemLeadingTriviaPieces | ||
.compactMap { Trivia(pieces:[ $0, .newlines(1)]) } | ||
.map { $0 + format._makeIndent() } | ||
.reduce(.zero, +) | ||
return ${base_type.syntax()}(result).withLeadingTrivia(indentedLeadingTrivia) | ||
} | ||
% end |
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 special handling for SourceFile
is sufficient here. The same issue will arise for any syntax node that doesn’t have a token before its first child node (I’m happy to be proven wrong by a test case). For example we should see the same issue if you try to create a TypealiasDecl
like the following where both the private
attribute and the TypealiasDecl
itself have comments attached to it.
/// This comment is added to the TypealiasDecl
/// This comment is added to the `private` keyword
private typealias MyInt = Int
Is there any issue with applying the solution you have implemented for SourceFile
to all syntax nodes (I haven’t checked).
Also, I think there’s a few end
missing and the ones that do exist have the wrong indentation level sometimes. Could you make sure that every if-statement has a matching end
on the same indentation level?
/// Extracts Lines from a given text, and add a prefix to it | ||
/// - Parameters: | ||
/// - text: Text which its lines will be prefixed | ||
/// - prefix: prefix for each line | ||
/// - Returns: an array of new string, which has prefixed lines | ||
private func prefixLines(of text: String, with prefix: String) -> [String] { | ||
|
||
let prefixedLines = text | ||
.split(whereSeparator: \.isNewline) | ||
.map {"\(prefix) \($0)"} | ||
|
||
return prefixedLines | ||
} |
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.
A little bit of a nitpick but I think this is a general helper method and not something that needs to access instance members of *Buildable
. Thus, I think it would make sense to make it a free function instead of an instance function.
/// - Parameter text: comment to be inserted after /// | ||
/// - Returns: a `${type.buildable()}` with the added documentation line comment. | ||
public func docLineComment(_ text: String) -> Self { | ||
let textWithSlashesPieces = prefixLines(of: text, with: "//") |
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.
This should probably be
let textWithSlashesPieces = prefixLines(of: text, with: "//") | |
let textWithSlashesPieces = prefixLines(of: text, with: "///") |
Could you also add a test case for it?
return addCommentPiece(commentPieces: [piece]) | ||
} | ||
|
||
private func addCommentPiece(commentPieces: [TriviaPiece]) -> Self { |
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.
Per the Swift Naming guidelines this should be named addingCommentPiece
because it returns a modified version of self
instead of modifying self
in-place.
// Plane node used to represent in a scene. | ||
// Each scene is supposed to have only one plane. |
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 great that this works! Could you also add a similar test case for block comments?
Superseded by #475 |
Update for `async` being a contextual keyword.
Hi everyone!
In this PR I'm discussing how we can add comments support in SwiftSyntaxBuilder based on my discussion here,
and as part of my interest in this project.
Goal
the goal is to be able to add comments to `BuildableNodes` in a declarative way, much like this:
Steps
to achieve this goal, I did the following
1- Added itemLeadingTrivia for each
BuildableNode
2- when the build function for a base type is called, i added the following(e.g BuildDecl)
3- added
withLineComment
, andwithDocBlokComment
on each BuildableNode, when called, they would return a new modified value of Self( to avoid mutating)Output
doing the following was enough to get our desired output 🤩:
Bugs and Questions 🐞
while everything work as expected, i noticed that the comments doesn't get genrated for first CodeBlockItem inside a
SourceFile {}
, as an example:also if there's only one Declaration/CodeBlockItem inside the SourceFile, the same problem happens
I'm still investigating it, I think it might be related to the way ResultBuilders work? would need your opinions about it.
I have checked Swift AST Explorer and found that it's expected for a first CodeBlockItem to have a comment as Leading Trivia,
also, what do you think about the direction I'm taking to solve and implement the comments support? should I continue with the rest of the comments type ? or there's something better I should do?
Thank you for your time reading this 🧑🏻💻