Skip to content

#841: fix NPE in DefectMissing when +unlint range references absent lint#843

Merged
yegor256 merged 4 commits intoobjectionary:masterfrom
bibonix:fix-841-npe-unlint-range-absent-lint
Apr 24, 2026
Merged

#841: fix NPE in DefectMissing when +unlint range references absent lint#843
yegor256 merged 4 commits intoobjectionary:masterfrom
bibonix:fix-841-npe-unlint-range-absent-lint

Conversation

@bibonix
Copy link
Copy Markdown
Contributor

@bibonix bibonix commented Apr 23, 2026

@yegor256 this fixes #841.

Problem. DefectMissing.apply threw NullPointerException when an EO file used +unlint name:line-line (range pattern) and name referred to a lint that never fired. this.defects.get(name) returned null in that case and the range branch dereferenced it via lines.stream() without a guard. The same class powers both LtUnlintNonExistingDefect and LtUnlintNonExistingDefectWpa, so both lints crashed instead of reporting the obviously wrong unlint.

Fix. In the range branch of DefectMissing.apply, when lines == null, treat the defect as missing unless the lint name is in the excluded set — mirroring what the non-range branch does in the same situation. I also added a defensive null check in the single-line branch so the two branches behave symmetrically; no existing code path depended on the previous behaviour there.

Tests (d35b9ed4, added before the fix, failing with the reported NPE).

  • DefectMissingTest#returnsTrueWhenRangeReferencesAbsentLint — unit-level reproduction.
  • DefectMissingTest#returnsFalseWhenRangeReferencesExcludedAbsentLint — covers the excluded interaction.
  • LtUnlintNonExistingDefectTest#catchesUnlintWithRangeForAbsentLint — the exact reproducer from the issue.

All three failed with the reported NPE before the fix and pass after it. The full local mvn test is green (639 tests, 0 failures). The mvn matrix job on CI is green on Ubuntu, Windows, and macOS; Qulice is also green.

Note on the reserved check. It is red on this PR and also on the other currently open PRs #838, #839, #840, #842. Locally I reproduced the same 3 failures + 1 error, all in HomeNamesTest / ReservedNamesTest / LtReservedNameTest, unrelated to this change. They are caused by the recent restructure of objectionary/home (2026-04-20, "Move objectionary files to objects directory"): .eo files now live directly under objects/ instead of objects/org/eolang/, so HomeNames.placeCsv's path filter objects/org/eolang no longer matches anything and the resulting reserved.csv is empty. That is a separate issue in this repo's reserved profile — it was already failing before this PR was opened and is out of scope here.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed null pointer exception when processing missing lint definitions
    • Improved error handling for ranged lint directives targeting non-existent lints
  • Improvements

    • Relaxed directory path requirements for EO source file discovery to support more flexible layouts
    • Broadened validation patterns for reserved object names, removing strict naming constraints
  • Tests

    • Expanded test coverage for missing lint and reserved name validation scenarios

claude added 2 commits April 23, 2026 12:18
…e references absent lint

A range-pattern +unlint (e.g. name:1-5) whose lint never fired causes
a NullPointerException in DefectMissing because this.defects.get(name)
returns null and no null-check is performed before lines.stream().

These tests cover both the unit level (DefectMissing) and the
integration level (LtUnlintNonExistingDefect) paths and currently
fail with NPE.
DefectMissing.apply threw NullPointerException when +unlint used the
range pattern name:line-line for a lint whose defects never fired. In
that case this.defects.get(name) returns null and the range branch
dereferenced it without a guard.

When lines is null, treat the defect as missing unless the name is in
the excluded set, matching the behaviour of the non-range branch for
the same situation. Also defends the single-line branch against a
null lines list for consistency.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@bibonix has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 11 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 33 minutes and 11 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eba6587c-d7a4-4adb-8ba3-0136dd8430a5

📥 Commits

Reviewing files that changed from the base of the PR and between a744074 and a8e01f3.

📒 Files selected for processing (1)
  • src/test/java/org/eolang/lints/LtReservedNameTest.java
📝 Walkthrough

Walkthrough

A null-pointer fix: DefectMissing.apply(String) now guards against this.defects.get(name) returning null and treats unlints referencing absent lint names (both single-line and range forms) using !excluded.contains(name) when defect lines are missing. Tests and name-filtering behavior updated accordingly.

Changes

Cohort / File(s) Summary
Defect missing logic
src/main/java/org/eolang/lints/DefectMissing.java
Added null-check for this.defects.get(name) and adjusted missing-condition logic for range (name:start-end) and single-line (name:line) unlint patterns to handle absent lint entries by falling back to !excluded.contains(name).
Unit tests — defect / unlint
src/test/java/org/eolang/lints/DefectMissingTest.java, src/test/java/org/eolang/lints/LtUnlintNonExistingDefectTest.java
Added tests covering range-based +unlint referencing absent lint names and ensuring no NPE; added test asserting behavior when absent lint is excluded.
Home names filtering & tests
src/main/groovy/org/eolang/lints/HomeNames.groovy, src/test/groovy/org/eolang/lints/HomeNamesTest.groovy, src/test/java/org/eolang/lints/LtReservedNameTest.java, src/test/java/org/eolang/lints/ReservedNamesTest.java
Broadened EO-file selection predicate in placeCsv() (removed strict objects/org/eolang requirement) and relaxed regexes used in reserved-name/home-name tests; adjusted one expected reserved-name message path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

core

Suggested reviewers

  • yegor256
  • maxonfjvipon
  • h1alexbel

Poem

🐰 A tiny guard hopped in today,
Lines that were missing now find their way.
No more crashes, no more fright,
Lints and ranges sleep well tonight. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive Changes to HomeNames.groovy, HomeNamesTest.groovy, LtReservedNameTest.java, and ReservedNamesTest.java appear to be collateral updates to reserved name validation patterns, which may be related to a separate home restructure mentioned in PR objectives. Clarify whether the reserved-name validation pattern relaxations are intentional scope additions or unrelated changes from objectionary/home; if unrelated, consider separating into a distinct PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly identifies the main change: fixing an NPE in DefectMissing when a +unlint range pattern references an absent lint, matching the scope of code changes.
Linked Issues check ✅ Passed The pull request successfully addresses issue #841 by adding null-guards in DefectMissing.apply to handle cases where range patterns reference absent lints, with comprehensive test coverage for both included and excluded cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/test/java/org/eolang/lints/LtUnlintNonExistingDefectTest.java (1)

226-246: Optional: also assert the reported defect identifies the missing lint.

The size check is enough to guard against the NPE regression, but asserting the single defect's rule() (or line) would pin down the semantic contract ("range +unlint for absent lint produces one non-existing-defect report") and prevent silent drift if the logic changes later.

Example additional assertion
final var defects = new LtUnlintNonExistingDefect(
    new ListOf<>(new LtAsciiOnly()),
    new ListOf<>()
).defects(
    new EoSyntax(
        String.join(
            "\n",
            "+unlint ascii-only:1-5",
            "[] > main",
            "  QQ.io.stdout > @",
            "    \"Hello\""
        )
    ).parsed()
);
MatcherAssert.assertThat(defects, Matchers.iterableWithSize(1));
MatcherAssert.assertThat(
    defects.iterator().next().rule(),
    Matchers.equalTo("unlint-non-existing-defect")
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/eolang/lints/LtUnlintNonExistingDefectTest.java` around
lines 226 - 246, Update the test catchesUnlintWithRangeForAbsentLint to also
assert the reported defect identifies the missing lint: call
LtUnlintNonExistingDefect.defects(...) into a local variable (e.g., defects),
keep the existing iterableWithSize(1) check, then assert
defects.iterator().next().rule() equals the expected rule string (e.g.,
"unlint-non-existing-defect"); reference the LtUnlintNonExistingDefect class,
the defects() method and the defect.rule() accessor to locate where to add the
assertion.
src/main/java/org/eolang/lints/DefectMissing.java (1)

59-63: Minor: lines == null check here is logically redundant.

When lines == null, name is absent from this.defects, so !names.contains(name) is already true and short-circuits the disjunction. The added || lines == null clause is harmless but dead. It's fine to keep it defensively for symmetry with the range branch and readability, but if you prefer minimal diffs you can drop it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/eolang/lints/DefectMissing.java` around lines 59 - 63, The
condition computing missing in DefectMissing.java contains a redundant "|| lines
== null" clause: when lines is null, !names.contains(name) is already true, so
remove the dead check from the branch that handles split.length > 1; update the
assignment to missing to use (!names.contains(name) ||
!lines.contains(Integer.parseInt(split[1]))) && !this.excluded.contains(name) so
the logic remains identical but without the redundant null test in the missing
computation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/org/eolang/lints/DefectMissing.java`:
- Around line 59-63: The condition computing missing in DefectMissing.java
contains a redundant "|| lines == null" clause: when lines is null,
!names.contains(name) is already true, so remove the dead check from the branch
that handles split.length > 1; update the assignment to missing to use
(!names.contains(name) || !lines.contains(Integer.parseInt(split[1]))) &&
!this.excluded.contains(name) so the logic remains identical but without the
redundant null test in the missing computation.

In `@src/test/java/org/eolang/lints/LtUnlintNonExistingDefectTest.java`:
- Around line 226-246: Update the test catchesUnlintWithRangeForAbsentLint to
also assert the reported defect identifies the missing lint: call
LtUnlintNonExistingDefect.defects(...) into a local variable (e.g., defects),
keep the existing iterableWithSize(1) check, then assert
defects.iterator().next().rule() equals the expected rule string (e.g.,
"unlint-non-existing-defect"); reference the LtUnlintNonExistingDefect class,
the defects() method and the defect.rule() accessor to locate where to add the
assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb4b37db-f3ea-4478-ac0c-7d25f177761e

📥 Commits

Reviewing files that changed from the base of the PR and between 38bb5f1 and 817ec18.

📒 Files selected for processing (3)
  • src/main/java/org/eolang/lints/DefectMissing.java
  • src/test/java/org/eolang/lints/DefectMissingTest.java
  • src/test/java/org/eolang/lints/LtUnlintNonExistingDefectTest.java

objectionary/home restructured on 2026-04-20 so that .eo files now live
under objects/ directly (e.g. objects/io/stdout.eo) instead of the old
objects/org/eolang/... nesting, and object paths no longer use the
org.eolang. prefix.

- HomeNames.placeCsv: widen the source filter to include any .eo file
  under objects/, not only under objects/org/eolang/.
- Tests: update the path-format expectations to drop the org.eolang.
  prefix (e.g. stdout is now reserved by "io.stdout.eo" rather than
  "org.eolang.io.stdout.eo").
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/test/java/org/eolang/lints/ReservedNamesTest.java (1)

38-38: Duplicate regex literal across two test files.

The identical pattern (?:[a-zA-Z_][a-zA-Z0-9_-]*\.)*[a-zA-Z_][a-zA-Z0-9_-]*\.eo is now hard-coded in both HomeNamesTest.groovy (Line 51) and here. Consider extracting it to a shared constant (e.g., a package-private constant in a test-support class) to keep both tests in sync when the reserved-name format evolves again.

Also, as noted on the Groovy test, this regex change appears unrelated to the #841 NPE fix this PR claims to address — consider moving it to a dedicated PR covering the objectionary/home restructure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/eolang/lints/ReservedNamesTest.java` at line 38,
ReservedNamesTest currently duplicates the regex literal used in HomeNamesTest;
extract the pattern into a shared test constant and reference it from both tests
to avoid drift. Create a package-private constant (e.g., TEST_EO_FILENAME_REGEX)
in a common test-support class (or a dedicated TestConstants class) and replace
the inline regex in ReservedNamesTest and HomeNamesTest with that constant;
update imports/usages to reference TestConstants.TEST_EO_FILENAME_REGEX and keep
the tests otherwise unchanged. Ensure the constant is named clearly and located
in a test-only package so production code is unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/test/java/org/eolang/lints/ReservedNamesTest.java`:
- Line 38: ReservedNamesTest currently duplicates the regex literal used in
HomeNamesTest; extract the pattern into a shared test constant and reference it
from both tests to avoid drift. Create a package-private constant (e.g.,
TEST_EO_FILENAME_REGEX) in a common test-support class (or a dedicated
TestConstants class) and replace the inline regex in ReservedNamesTest and
HomeNamesTest with that constant; update imports/usages to reference
TestConstants.TEST_EO_FILENAME_REGEX and keep the tests otherwise unchanged.
Ensure the constant is named clearly and located in a test-only package so
production code is unaffected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28ae5027-2683-46d9-881f-331031fdbb76

📥 Commits

Reviewing files that changed from the base of the PR and between 817ec18 and a744074.

📒 Files selected for processing (4)
  • src/main/groovy/org/eolang/lints/HomeNames.groovy
  • src/test/groovy/org/eolang/lints/HomeNamesTest.groovy
  • src/test/java/org/eolang/lints/LtReservedNameTest.java
  • src/test/java/org/eolang/lints/ReservedNamesTest.java
💤 Files with no reviewable changes (1)
  • src/main/groovy/org/eolang/lints/HomeNames.groovy
✅ Files skipped from review due to trivial changes (1)
  • src/test/java/org/eolang/lints/LtReservedNameTest.java

…xtures

The objectionary/home restructure dropped the org.eolang. prefix from
object paths; the hand-crafted MapOf<> fixtures in LtReservedNameTest
still carried the old "org.eolang.<name>.eo" strings and the expected
defect message in reportsCorrectMessageForReservedNameInTopObject was
hard-coded with the same prefix. Update them to match the new layout
so the fixtures reflect what the live scan actually produces.
@yegor256 yegor256 merged commit a927c03 into objectionary:master Apr 24, 2026
22 checks passed
@0crat
Copy link
Copy Markdown

0crat commented Apr 25, 2026

@bibonix Hey! Nice work on that contribution – you've bagged +16 points 🎉 The bonus calculation kicked in perfectly: you got the base 16 points for having reviews, plus the hit-of-code bonus, and avoided any deductions since you stayed within the sweet spot of our policy guidelines. Keep them coming! Your running score is +16 – don't forget to check your Zerocracy account too.

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.

NullPointerException when +unlint uses range pattern for absent lint in DefectMissing

4 participants