Pureline

Pureline Crystal

Own identity, explicit control flow.

Crystal keeps its own language identity while following Pureline principles. Short postfix guards remain allowed, complex control flow becomes an explicit block, nil is handled visibly, and macros stay the exception.

Idiomatic Postfix GuardsExplicit Blocks for Complex FlowNil GuardsType Inference
8Profile rules
16automated
Check Crystal code

Profile rules

PL-CR-001 Checker

Idiomatic Postfix Guards Are Allowed

Crystal postfix conditions are Pureline-compliant when they express a short, obvious guard. They keep guard logic compact and preserve a linear happy path.

Use an explicit block when either the condition or the guarded action becomes more complex. The rule is not “avoid postfix conditions.” The rule is: use the form that keeps the intent immediately obvious.

Avoid
Compound condition
return unless session && session.valid? && !session.expired? && limiter.allow?(user)
Not a guard action
logger.info("Session #{session.id} closed") if session.closed?
Double negation
return unless !closed
Pureline
Short guard
return unless valid
return if closed
Complex: explicit block
if session.closed? || session.expired?
  logger.info("Session is no longer usable")
  return
end
PL-CR-002 Checker

Avoid `else`

As in all Pureline profiles: handle the special case first, exit early, then continue linearly.

Avoid
if valid
  execute
else
  reject
end
elsif chain
if admin?
  grant_all
elsif member?
  grant_default
else
  deny
end
Pureline
if !valid
  reject
  return
end

execute
Real state branching
case role
when .admin?  then grant_all
when .member? then grant_default
end
PL-CR-003 Checker

Idiomatic Naming

Variables and methods use snake_case; types use PascalCase.

Avoid
class Session_Service
  def loadUser(userId)
    packetRegistry = PacketRegistry.new
  end
end
Pureline
Methods & variables
session_service
packet_registry
load_user
create_session
Types
SessionService
PacketRegistry
UserStorage
PL-CR-004 Checker

Prefer Type Inference

Crystal reliably infers local types. Redundant annotations are noise.

Avoid
session : Session = create_session(user)
Pureline
session = create_session(user)
PL-CR-005 Checker

Explicit Nil Guards

Expected nil cases are handled visibly instead of being buried in deeply nested chains or behind not_nil!.

Avoid
nil forced away
session = create_session(users.find(id).not_nil!)
nil buried in chains
name = users.find(id).try &.profile.try &.display_name.try &.strip
Pureline
user = users.find(id)

if user.nil?
  return
end

session = create_session(user)
As a short postfix guard
user = users.find(id)
return if user.nil?

session = create_session(user)
PL-CR-006 Checker

Exceptions Are for Exceptional States

Expected outcomes should preferably use normal domain modeling such as nil, union types, result-like structures, or enums. Exceptions represent exceptional failure, not ordinary branching.

Avoid
Exception as branching
def find_user(id)
  users.fetch(id)
rescue KeyError
  nil
end
Catch everything
begin
  connect
rescue
  retry_later
end
Pureline
def find_user(id) : User?
  users[id]?
end
PL-CR-007 Checker

Avoid Macro Cleverness Without Need

Crystal macros are powerful, but Pureline avoids hiding ordinary behavior behind metaprogramming unless the abstraction produces a clear architectural benefit. Generated behavior should remain predictable and discoverable.

Avoid
macro delegate_all(target)
  {% for method in @type.methods %}
    def {{method.name}}
      @{{target}}.{{method.name}}
    end
  {% end %}
end
Pureline
Standard macro, explicitly named
delegate open, close, to: @connection
PL-CR-008

Folder Density

A Crystal source or namespace folder may contain at most **10 production .cr files** by default.

Specs, generated files, bindings, and metadata-only files do not count.

When the limit is exceeded, split the namespace by responsibility while preserving idiomatic Crystal structure.

Core rules in the Crystal checker

In addition to the profile rules, the Crystal checker verifies these rules from the Pureline Core: