Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Docs

on:
push:
branches: [main]

permissions:
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- run: bundle exec yard doc
- uses: actions/upload-pages-artifact@v3
with:
path: doc

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
9 changes: 9 additions & 0 deletions .yardopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--output-dir doc
--markup markdown
--no-private
--protected
lib/**/*.rb
-
README.md
CONTRIBUTING.md
LICENSE
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ group :development do
gem 'pry', '~> 0.14.1'
gem 'ostruct', '~> 0.6.2'
gem 'yard', '~> 0.9'
gem 'webrick', '~> 1.8'
end
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Rubyzen

[![Gem Version](https://badge.fury.io/rb/rubyzen-lint.svg)](https://badge.fury.io/rb/rubyzen-lint)
[![CI](https://github.com/perrystreetsoftware/Rubyzen/actions/workflows/tests.yml/badge.svg)](https://github.com/perrystreetsoftware/Rubyzen/actions/workflows/tests.yml)
[![Docs](https://img.shields.io/badge/docs-yard-blue)](https://perrystreetsoftware.github.io/Rubyzen)

Rubyzen is an architectural linter for Ruby that lets you write architectural lint rules as unit tests, inspired by [Konsist](https://github.com/LemonAppDev/konsist) (for Kotlin) and [Harmonize](https://github.com/perrystreetsoftware/Harmonize) (for Swift).

## Architectural linters in the era of AI-generated code
Expand Down
1 change: 1 addition & 0 deletions lib/rubyzen/cache/parse_cache.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'digest'

module Rubyzen
# Caching utilities for parsed AST results.
module Cache
# In-memory cache for parsed AST results, keyed by file path and SHA256 checksum.
# Automatically invalidates entries when file contents change.
Expand Down
1 change: 1 addition & 0 deletions lib/rubyzen/collections/base_collection.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Rubyzen
# Typed collections that wrap arrays of declarations with filtering and aggregation.
module Collections
# Base collection class for all Rubyzen collections.
# Extends Array and replaces +select+/+reject+ with a single +filter+ method
Expand Down
1 change: 1 addition & 0 deletions lib/rubyzen/declarations/file_declaration.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Rubyzen
# Domain objects wrapping AST nodes with high-level accessors.
module Declarations
# Represents a parsed Ruby source file. This is the root of the declaration
# hierarchy — all other declarations are accessed through a FileDeclaration.
Expand Down
7 changes: 7 additions & 0 deletions lib/rubyzen/matchers/matcher_helpers.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Rubyzen
# Custom RSpec matchers for asserting on Rubyzen collections.
module Matchers
# Shared helper methods used by Rubyzen's custom RSpec matchers.
#
Expand Down Expand Up @@ -152,6 +153,12 @@ def formatted_matcher_groups
sections.join("\n")
end

# @!method message_for_failure(base_message)
# Formats the failure message by combining the base message with
# custom messages and classified item details (violations, stale entries).
#
# @param base_message [String] the default failure message
# @return [String] formatted failure message
def self.included(base)
base.define_method(:message_for_failure) do |base_message|
return @failure_message if @failure_message
Expand Down
30 changes: 20 additions & 10 deletions lib/rubyzen/matchers/zen_empty_matcher.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# Custom RSpec matcher that asserts a Rubyzen collection is empty.
#
# Used in architectural lint rules to verify that no items match
# a forbidden pattern (e.g., no controllers call +.where+ directly).
#
# @example Ensure no controllers use .where
# expect(controllers.all_methods.call_sites.with_name('where')).to zen_empty
#
# @example With a custom failure message
# expect(violations).to zen_empty("Controllers should not call .where directly")
# @!parse
# module Rubyzen
# module Matchers
# # Asserts that a Rubyzen collection is empty.
# #
# # Used in architectural lint rules to verify that no items match
# # a forbidden pattern (e.g., no controllers call +.where+ directly).
# #
# # @param custom_message [String, nil] optional failure message
# # @param allowlist [Array<String>, nil] items to permanently ignore
# # @param baseline [Array<String>, nil] known violations for gradual adoption
# #
# # @example Ensure no controllers use .where
# # expect(controllers.all_methods.call_sites.with_name('where')).to zen_empty
# #
# # @example With baseline for gradual adoption
# # expect(violations).to zen_empty(baseline: ['LegacyController'])
# def zen_empty(custom_message = nil, allowlist: nil, baseline: nil); end
# end
# end
RSpec::Matchers.define :zen_empty do |custom_message=nil, allowlist: nil, baseline: nil|
include Rubyzen::Matchers::MatcherHelpers

Expand Down
34 changes: 21 additions & 13 deletions lib/rubyzen/matchers/zen_false_matcher.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
# Custom RSpec matcher that asserts a block returns false for every item in a collection.
#
# Supports +allowlist:+ and +baseline:+ for gradual adoption, matching items
# where the block returns true against exception lists.
#
# @example Ensure no methods have more than 5 parameters
# expect(methods).to zen_false { |m| m.parameters.size > 5 }
#
# @example With a custom failure message
# expect(controllers.all_methods.call_sites).to zen_false("Controllers must not call .where directly") { |cs| cs.name == 'where' }
#
# @example With a baseline for gradual adoption
# expect(classes).to zen_false(baseline: ['LegacyModel']) { |k| k.lines_of_code > 200 }
# @!parse
# module Rubyzen
# module Matchers
# # Asserts that a block returns false for every item in a collection.
# #
# # Supports +allowlist:+ and +baseline:+ for gradual adoption, matching items
# # where the block returns true against exception lists.
# #
# # @param custom_message [String, nil] optional failure message
# # @param allowlist [Array<String>, nil] items to permanently ignore
# # @param baseline [Array<String>, nil] known violations for gradual adoption
# # @yield [item] block that should return false for each item
# #
# # @example Ensure no methods have more than 5 parameters
# # expect(methods).to zen_false { |m| m.parameters.size > 5 }
# #
# # @example With a baseline for gradual adoption
# # expect(classes).to zen_false(baseline: ['LegacyModel']) { |k| k.lines_of_code > 200 }
# def zen_false(custom_message = nil, allowlist: nil, baseline: nil, &block); end
# end
# end
RSpec::Matchers.define :zen_false do |custom_message=nil, allowlist: nil, baseline: nil|
include Rubyzen::Matchers::MatcherHelpers

Expand Down
25 changes: 18 additions & 7 deletions lib/rubyzen/matchers/zen_true_matcher.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
# Custom RSpec matcher that asserts a block returns true for every item in a collection.
#
# @example Ensure all methods have parameters
# expect(methods).to zen_true { |m| m.parameters? }
#
# @example With a custom failure message
# expect(services).to zen_true("All services must inherit from BaseService") { |s| s.superclass_name == 'BaseService' }
# @!parse
# module Rubyzen
# module Matchers
# # Asserts that a block returns true for every item in a collection.
# #
# # @param custom_message [String, nil] optional failure message
# # @param allowlist [Array<String>, nil] items to permanently ignore
# # @param baseline [Array<String>, nil] known violations for gradual adoption
# # @yield [item] block that should return true for each item
# #
# # @example Ensure all methods have parameters
# # expect(methods).to zen_true { |m| m.parameters? }
# #
# # @example With a custom failure message
# # expect(services).to zen_true("All services must inherit from BaseService") { |s| s.superclass_name == 'BaseService' }
# def zen_true(custom_message = nil, allowlist: nil, baseline: nil, &block); end
# end
# end
RSpec::Matchers.define :zen_true do |custom_message=nil, allowlist: nil, baseline: nil|
include Rubyzen::Matchers::MatcherHelpers

Expand Down
1 change: 1 addition & 0 deletions lib/rubyzen/parsers/a_s_t_parser.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'rubocop-ast'

module Rubyzen
# Ruby source file parsing utilities.
module Parsers
# Singleton parser that converts Ruby source files into Rubyzen declarations
# using RuboCop's AST processing. Results are cached via {Cache::ParseCache}.
Expand Down
1 change: 1 addition & 0 deletions lib/rubyzen/providers/blocks_provider.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Rubyzen
# Mixins that add capabilities (call sites, blocks, attributes, etc.) to declarations.
module Providers
# Provides access to block expressions (do..end / {..}) within a declaration.
module BlocksProvider
Expand Down
1 change: 1 addition & 0 deletions lib/rubyzen/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module Rubyzen
# @return [String] the current gem version
VERSION = '0.1.0'
end
3 changes: 2 additions & 1 deletion rubyzen-lint.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Gem::Specification.new do |spec|

spec.metadata = {
'source_code_uri' => 'https://github.com/perrystreetsoftware/Rubyzen',
'bug_tracker_uri' => 'https://github.com/perrystreetsoftware/Rubyzen/issues'
'bug_tracker_uri' => 'https://github.com/perrystreetsoftware/Rubyzen/issues',
'documentation_uri' => 'https://perrystreetsoftware.github.io/Rubyzen'
}
end