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

Adds AttributeRule #847

Merged
merged 30 commits into from
Dec 13, 2016
Merged

Adds AttributeRule #847

merged 30 commits into from
Dec 13, 2016

Conversation

marcelofabri
Copy link
Collaborator

@marcelofabri marcelofabri commented Oct 16, 2016

In short, here's the logic I've implemented:

  • If the attribute is @IBAction or @NSManaged, it should always be on
    the same line as the declaration
  • If the attribute has parameters, it should always be on the line above
    the declaration
  • Otherwise:
    • if the attribute is applied to a variable, it should be on the same line
    • if it's applied to a type or function, it should be on the line above
    • if it's applied to an import (the only option is @testable import),
      it should be on the same line.

Check #846 for more details on the idea.

TODO:

  • Make tests green on Swift 2.2
  • Add tests for Swift 2.2 attributes
  • Handle parameter in @objc

}

private func validateTestableImport(file: File) -> [StyleViolation] {
let pattern = "@testable[\n]+\\s*import*"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there's no info in AST about imports:

$ sourcekitten structure --text "@testable import Module"                    
{
  "key.offset" : 0,
  "key.diagnostic_stage" : "source.diagnostic.stage.swift.parse",
  "key.length" : 23
}

let previousAttributes = try attributesFromPreviousLines(lineNumber - 1, file: file)

let alwaysInSameLineAttributes = ["@IBAction", "@NSManaged"]
let alwaysInNewLineAttributes = ["@available"]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

maybe these should be configured?

]
}

private func attributesFromPreviousLines(lineNumber: Int, file: File) throws -> [String] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't particularly like this, but it was the only way I figured out to get the texts for the attributes, as there aren't exposed on AST: #846 (comment)

@marcelofabri
Copy link
Collaborator Author

When running the tests on SwiftLintFramework they pass, but on swiftlint they don't 😱

@marcelofabri
Copy link
Collaborator Author

marcelofabri commented Oct 16, 2016

The failing cases are:

"@GKInspectable\n var maxSpeed: Float"
"@discardableResult func a() -> Int"
"@objc\n @discardableResult func a() -> Int"
"@objc\n\n @discardableResult\n func a() -> Int"

I think they're failing because they were only introduced on Swift 3 (at least @discardableResult - couldn't find information about @GKInspectable). Is that expected?

@norio-nomura
Copy link
Collaborator

norio-nomura commented Oct 29, 2016

When running the tests on SwiftLintFramework they pass, but on swiftlint they don't 😱

swiftlint scheme has environment settings since 997de45 and 940fa37, but SwiftLintFramework scheme does not.
So, testing on Xcode 8.0 or later, swiftlint scheme uses SourceKit from Swift 2.3, SwiftLintFramework scheme uses from Swift 3.x (default).

@marcelofabri
Copy link
Collaborator Author

Thanks for explaining it, now it makes sense!

Should we wait on this until we support Swift 3.0 officially then?

@norio-nomura
Copy link
Collaborator

Should we wait on this until we support Swift 3.0 officially then?

IMO, we can use #if swift(>=3.0) to disable failing examples.

@marcelofabri marcelofabri changed the title Adds AttributeRule [WIP] Adds AttributeRule Nov 23, 2016
@codecov-io
Copy link

codecov-io commented Nov 23, 2016

Current coverage is 82.00% (diff: 94.78%)

Merging #847 into master will increase coverage by 0.86%

@@             master       #847   diff @@
==========================================
  Files           123        127     +4   
  Lines          5853       6263   +410   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           4749       5136   +387   
- Misses         1104       1127    +23   
  Partials          0          0          

Powered by Codecov. Last update c5bcece...46c69e6

@jpsim
Copy link
Collaborator

jpsim commented Nov 25, 2016

@marcelofabri thanks for your work on this. What's the next step?

@marcelofabri
Copy link
Collaborator Author

marcelofabri commented Nov 25, 2016

I need to detect and handle attributes with parameters (I think only @objc would benefit for this now) as IMO attributes with parameters should be on their own lines as well.

Also, it'd be nice to have some feedback in the rules applied, as their reflect mostly my opinion + what I found in the docs examples.

@jpsim
Copy link
Collaborator

jpsim commented Nov 25, 2016

I need to detect and handle attributes with parameters (I think only @objc would benefit for this now) as IMO attributes with parameters should be on their own lines as well.

@available is also parameterized.

@marcelofabri
Copy link
Collaborator Author

I need to detect and handle attributes with parameters (I think only @objc would benefit for this now) as IMO attributes with parameters should be on their own lines as well.

@available is also parameterized.

@available is already handled in a special way: https://github.com/realm/SwiftLint/pull/847/files#diff-cd01cc2805cb6eda59b220cf4a7b0648R185. (Although we could remove that when the parameters are handled)

@marcelofabri marcelofabri changed the title [WIP] Adds AttributeRule Adds AttributeRule Nov 27, 2016
@marcelofabri
Copy link
Collaborator Author

@jpsim This is ready for review. It got waaaay more complex than I thought it would be, so feel free to throw any ideas or suggestions to make it better 💯

Copy link
Collaborator

@jpsim jpsim left a comment

Choose a reason for hiding this comment

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

Great stuff! I have lots of comments, but I'm generally very happy to see how you did this.

@@ -43,6 +43,23 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#883](https://github.com/realm/SwiftLint/issues/883)

* Add `AttributesRule` which validates if an attribute (`@objc`, `@IBOutlet`,
`@discardableResult`, etc) is on the right position:
Copy link
Collaborator

Choose a reason for hiding this comment

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

"in the right position"

- if it's applied to a type or function, it should be on the line above
- if it's applied to an import (the only option is `@testable import`),
it should be on the same line.
You can also configure what attributes should be always in a new line or in
Copy link
Collaborator

Choose a reason for hiding this comment

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

"on a new line or on the same line"

- if it's applied to an import (the only option is `@testable import`),
it should be on the same line.
You can also configure what attributes should be always in a new line or in
the same line as the declaration with the `always_in_same_line` and
Copy link
Collaborator

Choose a reason for hiding this comment

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

always_on_same_line

it should be on the same line.
You can also configure what attributes should be always in a new line or in
the same line as the declaration with the `always_in_same_line` and
`always_in_new_line` keys.
Copy link
Collaborator

Choose a reason for hiding this comment

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

always_on_line_above?


private func validateTestableImport(file: File) -> [StyleViolation] {
let pattern = "@testable[\n]+\\s*import"
let excludingKinds = SyntaxKind.commentAndStringKinds()
Copy link
Collaborator

Choose a reason for hiding this comment

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

@testable is always a source.lang.swift.syntaxtype.attribute.builtin and import is always a source.lang.swift.syntaxtype.keyword, so you should use those rather than excluding comments and strings.

} else {

// ignore whitelisted attributes
let operation: Set<String> -> Set<String> -> Set<String> =
Copy link
Collaborator

Choose a reason for hiding this comment

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

operation is a very generic name for this. Can you find a better name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I couldn't find a better name 😅
Maybe we should drop this clever way and just have an if with two paths?

Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe call this let excludeWhitelistedAttributes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't know, the more I think the more I realize this is too clever. Are you ok with splitting it in two paths?

Copy link
Collaborator

Choose a reason for hiding this comment

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

sure

StyleViolation(ruleDescription: self.dynamicType.description,
severity: configuration.severityConfiguration.severity,
location: location
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

open parens hugs the parameters, so should the closing one.


#if swift(>=3.0)
let swift3Only = [
"@GKInspectable var maxSpeed: Float",
Copy link
Collaborator

Choose a reason for hiding this comment

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

indentation


#if swift(>=3.0)
let swift3Only = [
"@GKInspectable\n ↓var maxSpeed: Float",
Copy link
Collaborator

Choose a reason for hiding this comment

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

indentation

@@ -40,7 +40,7 @@ public struct CyclomaticComplexityRule: ASTRule, ConfigurationProviderRule {

public func validateFile(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
if !functionKinds.contains(kind) {
if !SwiftDeclarationKind.functionKinds().contains(kind) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

sweet refactoring!

previousAttributes.intersect(alwaysOnNewLineAttributes)
)
)(attributesTokens.intersect(alwaysOnSameLineAttributes))
let attributesAfterWhitelist: Set<String>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, this is definitely clearer!

@marcelofabri
Copy link
Collaborator Author

@jpsim I'll take a look at this, thanks for letting me know!

@marcelofabri
Copy link
Collaborator Author

marcelofabri commented Dec 9, 2016

@jpsim I was able to reduce the violations to 10:

/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/List.swift:36:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:291:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:295:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:302:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:309:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:315:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:324:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:360:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/RealmSwift/Object.swift:365:19: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)
/Users/marcelofabri/Downloads/realm-cocoa-2.1.1/Realm/Swift/RLMSupport.swift:23:25: warning: Attributes Violation: Attributes should be on their own lines in functions and types, but on the same line as variables and imports. (attributes_rule)

which are all funcs with @objc or @nonobjc in the same line as the declaration.

I'll commit it shortly.

@jpsim
Copy link
Collaborator

jpsim commented Dec 12, 2016

Can you please rebase this so we can confirm it builds and passes tests on Linux?

@marcelofabri
Copy link
Collaborator Author

marcelofabri commented Dec 12, 2016

The tests are failing on Linux because of

guard let config = makeConfig(ruleConfiguration, ruleDescription.identifier) else {
    XCTFail()
    return
}

Any ideas? 😬

UPDATE: I was able to fix in fd09fde by using [String: Any] instead of [String: AnyObject] when casting the configuration object.

Copy link
Collaborator

@jpsim jpsim left a comment

Choose a reason for hiding this comment

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

Looks good, but I want to run on Realm again.

// AttributesRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 10/15/16.
Copy link
Collaborator

Choose a reason for hiding this comment

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

12/12/16?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is right actually 😅

// AttributesRulesExamples.swift
// SwiftLint
//
// Created by Marcelo Fabri on 09/12/16.
Copy link
Collaborator

Choose a reason for hiding this comment

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

12/12/16?

// AttributesConfiguration.swift
// SwiftLint
//
// Created by Marcelo Fabri on 26/11/16.
Copy link
Collaborator

Choose a reason for hiding this comment

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

12/12/16?

@jpsim
Copy link
Collaborator

jpsim commented Dec 12, 2016

Personally, I'd keep @objc and @nonobjc on the same line, so I'm trying to configure Realm's .swiftlint.yml to do so, but violations are still being reported:

attributes_rule:
  always_on_same_line:
    - '@objc'
    - '@nonobjc'

am I missing something?

@marcelofabri
Copy link
Collaborator Author

@jpsim I've checked why violations are still being reported and almost all of them seems to be because the attribute had a parameter. Another one was because there was two properties without blank lines between them, so the algorithm thought that the attributes from both of them belonged to the last one:

@objc private var object: RLMWeakObjectHandle?
@objc private var property: RLMProperty?

I'll try to fix these soon.
(Ugh, this rule is complicated 😅)

return false
}

return ["func", "var", "let"].contains(keyword)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this probably could be a bit safer if I used AST, but I thought it would be more complex and slower, since I already had the tokens.

@marcelofabri
Copy link
Collaborator Author

@jpsim OK, so I had another look on this and fixed a bug in e4a556d. The remaining violations of this rule on Realm with the updated .swiftlint.yml are because of @objc with parameters in a new line, when the configuration says they should be on the same line always. For example:

@objc(RealmSwiftObjectUtil)
public class ObjectUtil

We could update the rule implementation to prioritize the "attributes with parameters should be on their own line" over the always_on_same_line configuration (and maybe always_on_new_line?), but that might be inconvenient for a few people.

In reality, there're so many cases that this rule is probably the most complex one in SwiftLint, so I understand if you feel we shouldn't merge this or make it opt-in.

@jpsim
Copy link
Collaborator

jpsim commented Dec 13, 2016

In reality, there're so many cases that this rule is probably the most complex one in SwiftLint, so I understand if you feel we shouldn't merge this or make it opt-in.

I'm thinking opt-in might make sense for those reasons.

@marcelofabri
Copy link
Collaborator Author

I've updated it to be opt-in and also renamed the identifier to attributes as no other rule has _rule in the identifier.

@@ -26,7 +26,7 @@ public struct AttributesRule: ASTRule, ConfigurationProviderRule {
public init() {}

public static let description = RuleDescription(
identifier: "attributes_rule",
identifier: "attributes",
Copy link
Collaborator

Choose a reason for hiding this comment

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

👌

Copy link
Collaborator

@jpsim jpsim left a comment

Choose a reason for hiding this comment

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

👍 this looks good to me now. @marcelofabri I'll let you merge this when you're done working on it.

@marcelofabri
Copy link
Collaborator Author

Let's just wait for CI to be sure 😅

@marcelofabri
Copy link
Collaborator Author

Giphy

@marcelofabri marcelofabri merged commit 4f4c6c6 into realm:master Dec 13, 2016
@marcelofabri marcelofabri deleted the attributes-rule branch December 13, 2016 04:05
@jpsim
Copy link
Collaborator

jpsim commented Dec 13, 2016

🎉 congrats @marcelofabri, I know this was a massive undertaking.

@marcelofabri marcelofabri mentioned this pull request Dec 19, 2016
3 tasks
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.

4 participants