Pureline

The core.

These rules apply to all supported languages as far as the language allows them naturally. The language profiles build on them.

Linear FlowExplicit IntentMinimal NoiseSmall UnitsClear OwnershipCohesion over Size

Pureline standardizes principles, not syntax. Every language stays idiomatic: Pureline removes unnecessary noise without stripping away the features that make Java, JavaScript, TypeScript, Go, Crystal, Emvus, and Rust effective.

Core rules

Control Flow

PL-CF-001 Checker

Prefer Guard Clauses

Special cases leave the function early. The happy path stays as close to the base indentation level as possible.

Reading flow: Input → Validation → Transformation → Execution → Result. Not: if → if → else → if → callback → branch.

Avoid
if (request != null) {
    if (request.valid()) {
        var session = sessions.create(request.user());
        return executor.execute(session, request);
    } else {
        return Result.invalid();
    }
}

return Result.empty();
Pureline
if (request == null) {
    return Result.empty();
}

if (!request.valid()) {
    return Result.invalid();
}

var session = sessions.create(request.user());
return executor.execute(session, request);
PL-CF-002 Checker

Avoid else

Instead of an else branch, invert the condition, handle the special case, and leave the block.

A ternary expression may be used for simple value selection. Complex nested ternaries are not Pureline.

Avoid
if (valid) {
    execute();
} else {
    reject();
}
Nested ternary
var state = valid ? (open ? State.READY : State.CLOSED) : failed ? State.FAILED : State.INVALID;
Pureline
if (!valid) {
    reject();
    return;
}

execute();
Allowed: simple value selection
var state = valid ? State.READY : State.INVALID;
PL-CF-003 Checker

Explicit Blocks in Brace-Based Languages

In Java, JavaScript, TypeScript, Go, Emvus, and other brace-based languages, every control-flow body uses an explicit block, even for a single statement. This applies to if, for, while, and comparable constructs.

The rule does not ban idiomatic shorthand in languages where it is a normal, readable part of the language. Simple Crystal postfix guards such as return unless valid are Pureline-compliant.

Avoid
Inline body
if (!valid) return;
Indented one-liner
if (!valid)
    return;
Loop without a block
for (const user of users) notify(user);
Pureline
if (!valid) {
    return;
}
for (const user of users) {
    notify(user);
}
Idiomatic shorthand in Crystal
return unless valid
PL-CORE-005

Idiomatic Shorthand When It Stays Immediately Readable

Pureline standardizes clarity and structure, not identical syntax across all languages. Shorthand that is established in a language remains allowed as long as the condition and action are simple.

As soon as a shorthand requires thought, it becomes an explicit block.

Avoid
Shorthand with too much logic
notify_admins(session, reason: :expired) unless session.valid? || session.renewable? && !user.banned?
Pureline
return unless session.valid?
Complex: explicit block
if session.closed? || session.expired?
  logger.info("Session is no longer usable")
  return
end

Names, Functions & Structure

PL-NAME-001 Checker

Names Must Be Meaningful

As short as possible, as explicit as necessary. Cryptic abbreviations are just as bad as names that turn into sentences.

Exceptions are established local conventions such as loop counters (i, j, k).

Avoid
Meaningless
r   s   p   x   tmp   obj   data2   thing
Overly long
currentIncomingPlayerConnectionRequestObject
In code
var tmp = sessions.find(r.id());
var data2 = tmp.user();
Pureline
request   session   player   packet   handler
storage   result    connection   worker
PL-FN-001 Checker

One Clear Responsibility per Function

A function represents one clearly identifiable action. Vague names reveal that it does too much.

Size heuristic: ~5–30 lines normal, ~30–50 lines review, 50+ lines usually split. These are heuristics, not absolute limits.

Avoid
Vague names
handleEverything()
process()
doStuff()
executeAll()
One function, three jobs
public void process(Request request) {
    validate(request);
    var session = sessions.create(request.user());
    storage.save(session);
    mailer.sendWelcome(request.user());
    metrics.increment("sessions");
}
Pureline
validateRequest()
createSession()
sendPacket()
loadUser()
closeConnection()
One clearly named action
public Session openSession(Request request) {
    var session = sessions.create(request.user());
    storage.save(session);
    return session;
}
PL-STRUCT-001 Checker

One Clear Responsibility per Class and Module

Manager classes that bundle database, players, packets, commands, configuration, HTTP, and logging are God Classes. Code is cut along domain responsibility.

A class has one clear responsibility when its members and internal state contribute to one cohesive concern. Line count alone does not determine whether a class is well designed; see PL-STRUCT-003 for size guidance.

Avoid
God Class
ServerManager
 ├─ database
 ├─ players
 ├─ packets
 ├─ commands
 ├─ configuration
 ├─ HTTP
 └─ logging
Pureline
server/     ServerRuntime
network/    Connection, PacketRegistry, PacketDispatcher
session/    Session, SessionService
storage/    UserStorage, SessionStorage
PL-STRUCT-002

Keep Namespaces Small Enough to Scan

File and type density are part of readability. A package or source folder should be small enough that its contents can be understood at a glance, without searching a long flat list of unrelated files.

When the limit is exceeded, split by responsibility instead of letting the folder become a dumping ground. A split should introduce a real responsibility boundary, not arbitrary nesting.

Default limits: Java 8 production top-level types/source files per package; Emvus 8 .ev files per source/namespace folder; JavaScript and TypeScript 10 production modules per feature/source folder; Go 12 non-test, non-generated .go files per package directory; Crystal 10 .cr files per folder; Rust 10 hand-maintained .rs files per feature/source folder.

Not counted: tests and specs, generated code, metadata-only files, package-info.java, module-info.java, barrel files such as index.ts/index.js, generated bindings or protocol output, and migration snapshots and comparable machine-generated artifacts.

A project may override the thresholds through a future Pureline configuration file; the default remains intentionally strict.

Avoid
14 files in one package
network/
    Connection.java
    ConnectionFactory.java
    ConnectionState.java
    Packet.java
    PacketCodec.java
    PacketDecoder.java
    PacketEncoder.java
    PacketRegistry.java
    PacketDispatcher.java
    PacketHandler.java
    PacketQueue.java
    PacketMetrics.java
    SocketConfig.java
    SocketRuntime.java
Pureline
Split by responsibility
network/
    Connection.java
    ConnectionFactory.java
    ConnectionState.java

network/packet/
    Packet.java
    PacketCodec.java
    PacketDecoder.java
    PacketEncoder.java
    PacketRegistry.java
    PacketDispatcher.java

network/runtime/
    PacketQueue.java
    PacketMetrics.java
    SocketConfig.java
    SocketRuntime.java
PL-STRUCT-003

Cohesion over Size

Cohesion over Size: the preferred maximum class size is guidance, never a hard limit. Strongly related logic stays together when splitting it would make the code harder to read, maintain, test, or reason about. A larger cohesive class is preferable to multiple artificial classes connected by unnecessary delegation.

Guidance: up to 200 lines is the preferred normal case; 201–400 lines is unproblematic when the class has a clear responsibility; 401–600 lines is a deliberate architectural decision whose cohesion should stay understandable; above 600 lines a review is recommended, but nothing is prohibited. These are heuristics: crossing a threshold is not a style violation by itself and does not require a split.

Larger classes are justified for parsers and lexers (a cohesive state machine gets more complicated when states and transitions are scattered across artificial abstractions), for algorithms with tightly coupled processing phases, for central data structures whose methods share the same invariants and internal state, and for complex but cohesive logic where a split only adds indirection, delegation, and coordination without more clarity.

Split anyway along real responsibility boundaries, not by line count. Indicators are distinct subsystems evolving independently, unrelated reasons to change, weakly coupled groups of methods, or separate lifecycles obscured by one class.

Tooling: Pureline Checker and IDE inspections may show an informational size notice with a cohesion-review suggestion, particularly above 600 lines. They must not treat line count as sufficient evidence of a violation, automatically extract classes, or require suppressions merely to keep a cohesive class intact. The principle applies to all language profiles (Java, JavaScript, TypeScript, Go, Crystal, Rust, Emvus); language-specific syntax and idioms continue to take precedence.

Avoid
Artificial split caused only by line count
Lexer
 ├─ LexerStateA   (delegates to LexerHelper)
 ├─ LexerStateB   (delegates to LexerHelper)
 └─ LexerHelper   (shares all states with both)
Pureline
Cohesive, even if large
Lexer          // one state machine, ~500 lines, one responsibility
Split anyway: real boundaries
ReportService
 ├─ PDF rendering       → ReportRenderer
 ├─ Mail delivery      → ReportMailer
 └─ Billing rules       → BillingPolicy
PL-ARCH-001

Abstraction Requires a Reason

Duplication alone does not justify an abstraction. Good reasons are: multiple real implementations, clear API boundaries, platform-specific implementations, test seams, dependency inversion, and shared domain contracts.

Avoid
AbstractBaseFactoryManagerProvider
Interface without a second implementation
public interface ISessionFactory {
    Session create(User user);
}

public final class SessionFactoryImpl implements ISessionFactory {
    public Session create(User user) {
        return new Session(user);
    }
}
Pureline
Direct until there is a real reason
var session = new Session(user);
Good reasons
multiple real implementations
clear API boundaries
platform-specific implementations
test seams
dependency inversion
shared domain contracts

Formatting & Comments

PL-FMT-001 Checker

Prefer Compact Horizontal Calls

Calls stay horizontal as long as they are readable. Arguments are not written one per line just because it is possible.

Vertical formatting is allowed when it reveals meaningful semantic structure, for example in builders.

Avoid
var box = Block.box(
    min,
    min,
    max,
    max,
    max,
    16
);
Pureline
var box = Block.box(min, min, max, max, max, 16);
Semantic grouping
var server = Server.builder()
    .host(config.host())
    .port(config.port())
    .executor(executor)
    .build();
PL-DOC-001 Checker

Comments Explain Why, Not What

The code explains what happens. Comments explain why something unusual is necessary: protocol constraints, workarounds, external bugs, non-obvious mathematical behavior, or unusual performance constraints.

Avoid
Comment repeats the code
// Check if the player exists
if (player == null) {
    return;
}
Comment replaces a name
// timeout in seconds for the handshake
var t = 30;
Pureline
Comment explains the why
// The protocol allows only one retry, a second one gets the client banned.
if (attempts > MAX_RETRIES) {
    return;
}
Name instead of comment
var handshakeTimeout = Duration.ofSeconds(30);

Language & Values

PL-CORE-011

Modern Features Where They Improve Clarity

Modern language features are used when they make code shorter, safer, or easier to understand, not to look modern.

Avoid
String label;
switch (state) {
    case READY:
        label = "ready";
        break;
    default:
        label = "unknown";
}
Pureline
var label = switch (state) {
    case READY -> "ready";
    default -> "unknown";
};
PL-CORE-017

Prefer Framework-Native APIs

What the platform or framework already solves cleanly is not rebuilt. Custom wrappers need a real reason.

Avoid
function copy(value) {
    return JSON.parse(JSON.stringify(value));
}
Pureline
const copy = structuredClone(value);
PL-CORE-018

Avoid Magic Values

Values with meaning get a name. Values that may change operationally belong in configuration rather than deep in the implementation.

Avoid
if (attempts > 3) {
    connection.close(4008);
}
Pureline
if (attempts > MAX_LOGIN_ATTEMPTS) {
    connection.close(CloseCode.POLICY_VIOLATION);
}

Architecture & Runtime

Clear Dependency Direction

Dependencies flow in one direction: domain → service → infrastructure, or feature-oriented in modules such as session/, network/, storage/, command/, runtime/, api/.

Cycles such as network → session → storage → network are avoided.

Avoid
Cycle
network → session → storage → network
Domain knows infrastructure
public record Session(UUID id, JdbcTemplate jdbc) {
}
Pureline
domain
↓
service
↓
infrastructure
PL-CORE-015

Keep Side Effects Visible

Pureline does not require exposing every internal operation. It requires avoiding surprising hidden behavior.

Avoid
sessions.createAndPersistAndNotify(user);
Pureline
var user = users.load(id);
var session = sessions.create(user);

sessions.save(session);
events.publish(new SessionCreated(session.id()));
PL-CORE-010

Immutability by Default

Values are immutable by default. Mutation is intentional, local, and easy to identify: Java records, TypeScript readonly data, const in JavaScript, immutable value objects, and short-lived mutable local state.

Shared mutable state is avoided where practical.

Avoid
Shared, mutable state
public static List<Session> ACTIVE = new ArrayList<>();

session.setState(State.CLOSED);
Pureline
public record Session(SessionId id, State state) {
    public Session close() {
        return new Session(id, State.CLOSED);
    }
}
interface Session {
    readonly id: string;
    readonly state: SessionState;
}
PL-CORE-019

Explicit Dependencies

Dependencies are visible in constructors, function parameters, or explicit module wiring. Hidden global access and unnecessary service locators are avoided.

Dependency injection is encouraged, but a heavy DI framework is not required.

Avoid
Hidden global access
public Session open(User user) {
    var storage = ServiceLocator.get(IStorage.class);
    return storage.create(user);
}
Pureline
public final class SessionService {
    private final IStorage storage;

    public SessionService(IStorage storage) {
        this.storage = storage;
    }
}
PL-CORE-020

Performance with Intent

Readability is the default. Hot paths are optimized deliberately: avoid unnecessary allocations, avoid repeated reflection, cache expensive resolution, use appropriate collections, avoid hidden blocking, reduce copying, and keep ownership clear.

Optimizations must remain understandable.

Avoid
Reflection per packet in the hot path
public void dispatch(Packet packet) {
    var handler = packet.getClass().getMethod("handle");
    handler.invoke(packet);
}
Pureline
Resolve once, then look up
private final Map<Class<?>, IPacketHandler> handlers;

public void dispatch(Packet packet) {
    handlers.get(packet.getClass()).handle(packet);
}
PL-CORE-016 Checker

Resources with a Clear Lifecycle

Whatever is opened, registered, started, allocated, or subscribed has an equally clear way back. The language profiles make this concrete: try-with-resources in Java, defer in Go, cleanup paths in JavaScript.

Avoid
function watch(element) {
    window.addEventListener("resize", () => layout(element));
}
Pureline
function watch(element) {
    const onResize = () => layout(element);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
}

Review checklist

Is the code Pureline? The checklist covers what no checker can judge automatically.