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

Add StructInheritance cop #1571

Merged
merged 1 commit into from
Jan 19, 2015

Conversation

mmozuras
Copy link
Contributor

No description provided.

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch 4 times, most recently from 31dd5af to 1915c2b Compare January 14, 2015 13:14
if struct_construtor?(superclass)
add_offense(node, superclass.loc.expression, MSG)
end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

As travis says, when running Rubocop against this file:

lib/rubocop/cop/style/struct_inheritance.rb:20:11: C: Use a guard clause instead of wrapping the code inside a conditional expression.
          if struct_construtor?(superclass)
          ^^

So something like:

def on_class(node)
  _name, superclass, _body = *node
  return unless struct_construtor?(superclass)

  add_offense(node, superclass.loc.expression, MSG)
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bquorning done. Sorry, completely forgot to check back for Travis status.

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch from 1915c2b to e152a65 Compare January 15, 2015 20:22

private

def struct_construtor?(node)
Copy link
Contributor

Choose a reason for hiding this comment

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

Possible typo: struct_construtor?struct_constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed 😱

How did that happen... 😆

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch from 922ac13 to b9cea85 Compare January 16, 2015 08:25

it 'registers an offense when inheriting from Struct.new' do
inspect_source(cop,
['class Person < Struct.new(:first_name, :last_name)',
Copy link
Collaborator

Choose a reason for hiding this comment

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

There should be an example using Struct.new ... do ... end as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

You can subclass the result of Struct.new do end as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bbatsov ohhh... Got it, done 😄

@bbatsov
Copy link
Collaborator

bbatsov commented Jan 17, 2015

Overall the code looks good (apart from my remarks). Next step - checking for classes that can be replaced by a struct. :-)

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch 2 times, most recently from e2936cf to 33d6934 Compare January 17, 2015 15:47
@mmozuras
Copy link
Contributor Author

@bbatsov could you give an example of what you mean? 😄

@bbatsov
Copy link
Collaborator

bbatsov commented Jan 17, 2015

I meant that once we're ready with this cop we could create another one checking for code like:

class Person
  attr_accessor :age, :name

  def initialize(age, name)
    ...
  end
end

We could search for more complex patterns, but I think we shouldn't overdo this. Anyways, this was just a random comment. Let's wrap this PR first.

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch 4 times, most recently from d05ba1c to 4e48b70 Compare January 19, 2015 06:57
@mmozuras
Copy link
Contributor Author

@bbatsov changed the message to match the updated one in ruby-style-guide.

#
# @example
#
# # bad
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the example code be indented with two more spaces?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bbatsov 👍

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch from 4e48b70 to c71d5b6 Compare January 19, 2015 07:16
# # good
# Person = Struct.new(:first_name, :last_name)
class StructInheritance < Cop
MSG = "Don't extend an instance initialized by Struct.new. Extending " \
Copy link
Collaborator

Choose a reason for hiding this comment

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

One final remark. Keep just the first sentence of the MSG (long messages look bad with some formatters) and wrap Struct.new in backquotes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bbatsov done 😄

@mmozuras mmozuras force-pushed the add_struct_inheritance_cop branch from c71d5b6 to 67743e0 Compare January 19, 2015 07:31
bbatsov added a commit that referenced this pull request Jan 19, 2015
@bbatsov bbatsov merged commit 16f7ec5 into rubocop:master Jan 19, 2015
@bbatsov
Copy link
Collaborator

bbatsov commented Jan 19, 2015

👍 Thanks!

@mmozuras mmozuras deleted the add_struct_inheritance_cop branch January 19, 2015 08:15
@tamird
Copy link
Contributor

tamird commented Jan 29, 2015

There's a small gotcha with Struct.new, which I think this cop doesn't handle:

Person = Struct.new(:first, :last) do
  SEPARATOR = ' '.freeze
  def name
    [first, last].join(SEPARATOR)
  end
end

is not equivalent to:

class Person < Struct.new(:first, :last)
  SEPARATOR = ' '.freeze
  def name
    [first, last].join(SEPARATOR)
  end
end

The former creates ::Person and ::SEPARATOR, while the latter creates ::Person and ::Person::SEPARATOR.

@mocoso
Copy link

mocoso commented Feb 16, 2015

Why is it better not to inherit from Struct.new?

@Koronen
Copy link
Contributor

Koronen commented Feb 16, 2015

@mocoso:

Don't extend an instance initialized by Struct.new. Extending it introduces a superfluous class level and may also introduce weird errors if the file is required multiple times.

https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new

@mocoso
Copy link

mocoso commented Feb 16, 2015

@Koronen Thanks for the clarification (and for your very polite encouragement for me to read the docs) 😄

@nanodeath
Copy link

Just upgraded and got bit by this cop. Can the rule be updated to only address empty class blocks (e.g. no additional methods or constants)? As @tamird alluded to, there are valid use cases for extending Struct.new. Or at the very least, add more details about why this cop should be included and on by default.

@bbatsov
Copy link
Collaborator

bbatsov commented May 21, 2015

There's a link to the style guide already.

@nanodeath
Copy link

@bbatsov Yes, I saw that, and it didn't clarify anything.

@bbatsov
Copy link
Collaborator

bbatsov commented May 21, 2015

Guess you've never seen Struct.new do ...? Check out the official docs http://ruby-doc.org/core-2.2.0/Struct.html

@nanodeath
Copy link

I've seen that too -- it was in one of the examples earlier in this thread. But as was already pointed out, it's not equivalent.

I'm just looking for a reason for the hate for this obviously useful pattern.

@bbatsov
Copy link
Collaborator

bbatsov commented May 22, 2015

I understand this well enough. Perhaps we should account for the constant definition, but outside of this corner-case the two setups are pretty much the same and it's generally better to prefer the simpler option (simpler inheritance chain).

@bquorning
Copy link
Contributor

I just found this interesting article about defining constants inside a Struct: http://blog.55minutes.com/2012/01/trivia-constants-in-ruby-192-vs-193/

I think I agree with its conclusion: maybe you should just use a simple Class instead.

@bbatsov
Copy link
Collaborator

bbatsov commented May 22, 2015

Sounds good to me. @bquorning Please, log a ticket so we don't forget to implement a check for constants defined in structs eventually.

@nanodeath
Copy link

I was unaware of the perils of constants defined inside blocks, and I agree that constants inside blocks are definitely a risky choice and should be discouraged. I'm not convinced the author of that article knew about the < Struct.new pattern, so I wouldn't take his failure to mention it as a recommendation against it.

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.

7 participants