Skip to content

chore(internals): add spdx eslint rule#26

Merged
coryrylan merged 1 commit intomainfrom
topic-lint-fixup-1
Apr 24, 2026
Merged

chore(internals): add spdx eslint rule#26
coryrylan merged 1 commit intomainfrom
topic-lint-fixup-1

Conversation

@coryrylan
Copy link
Copy Markdown
Collaborator

@coryrylan coryrylan commented Apr 24, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new ESLint rule to enforce SPDX copyright and license headers in source files.
  • Documentation

    • Added documentation for the SPDX header ESLint rule.
  • Chores

    • Enhanced code generation to include SPDX headers in auto-generated files.
    • Improved notice file generation with formatting and optimized write behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

The pull request introduces SPDX license header enforcement across the codebase through a new ESLint rule, updates the NOTICE.md generation to format through Prettier with conditional file writing, registers the rule in TypeScript and CSS ESLint configurations, and adds SPDX headers to auto-generated icon registry files.

Changes

Cohort / File(s) Summary
ESLint require-spdx-header Rule
projects/internals/eslint/src/local/require-spdx-header.js, projects/internals/eslint/src/local/require-spdx-header.test.js
New ESLint rule validating SPDX headers (copyright and license lines) in source files, supporting both line and block comment styles. Comprehensive test suite covering TypeScript and CSS with auto-fix validation for missing/invalid headers.
ESLint Configuration
projects/internals/eslint/src/configs/typescript.js, projects/internals/eslint/src/configs/css.js, projects/internals/eslint/README.md
Registers require-spdx-header rule as error-level in both TypeScript and CSS configs; documents the rule behavior and year-preservation logic in README.
NOTICE.md Generation
projects/internals/ci/notice/index.js
Routes rendered Markdown through Prettier for consistent formatting and implements writeFileIfChanged logic to compare existing and new content, writing only when contents differ.
Auto-generated File Headers
projects/core/build/icons.js, projects/core/src/icon/icons.ts, projects/core/src/icon/server.ts
Injects SPDX copyright and license headers into icon registry files and their generated counterparts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A header for each file, so tidy and neat,
SPDX compliance makes our code complete!
Prettier formats, conditions do check,
No needless writes cluttering the deck.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore(internals): add spdx eslint rule' accurately summarizes the main change: adding a new ESLint rule for SPDX header validation to the internals project.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch topic-lint-fixup-1

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

import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs';
import path from 'path';
import * as url from 'url';
import prettier from 'prettier';
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Running prettier over any new notice file generation so it conforms to the repo wide rules and not create new changes after running

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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/core/eslint.config.js`:
- Line 13: The global ignores entry currently disables all linting for the two
files by using { ignores: ['src/icon/icons.ts', 'src/icon/server.ts'] }; instead
create a rule-specific override for local-typescript/require-spdx-header that
targets those two files so only that rule is disabled for them; update the
ESLint config to remove those file paths from the global ignores array and add
an overrides section with a glob matching the two files and set rules: {
'local-typescript/require-spdx-header': 'off' } (referencing the existing
ignores array and the rule name local-typescript/require-spdx-header to locate
where to change).

In `@projects/internals/eslint/README.md`:
- Line 77: Update the Vale vocabulary whitelist by adding the new SPDX-related
terms to the Elements vocabulary accept list: open
config/vale/styles/config/vocabularies/Elements/accept.txt and append the four
entries `SPDX`, `SPDX-FileCopyrightText`, `SPDX-License-Identifier`, and
`Apache-2.0` (one term per line) so the README's SPDX terminology is whitelisted
for Vale checks.

In `@projects/internals/eslint/src/local/require-spdx-header.js`:
- Around line 79-82: The loop that computes headerEnd (using headerStart,
headerEnd and the lines array) only advances while a line includes 'SPDX-' so it
stops when a blank line appears and can leave duplicate SPDX identifiers after
the fixer; change the logic to treat blank lines as part of the header scan by
advancing headerEnd while the current line is either empty/whitespace or
contains 'SPDX-' (i.e., skip over blanks between SPDX lines) so all existing
SPDX lines are included and removed/replaced by the fixer; apply the same change
to the other identical loop referenced around the second occurrence (the block
at lines 91-94) so both scans handle interleaved blank lines correctly.
- Around line 16-23: The regex literals for SPDX header matching duplicate the
holder/license text; update the four RegExp constructions (expectedCopyright,
expectedIdentifier, wrongStyleCopyright, wrongStyleIdentifier) to interpolate
the canonical constants (COPYRIGHT_HOLDER and LICENSE) instead of hard-coded
strings, ensuring you use the escaped forms (e.g., an existing LICENSE_ESCAPED
or escape the constant before embedding) so the single source of truth is used
for both expected and wrongStyle patterns; keep the surrounding open/wrongOpen
and close/wrongClose templates as-is and only replace the repeated literal
fragments with the constants.

In `@projects/internals/eslint/src/local/require-spdx-header.test.js`:
- Around line 75-76: Introduce a single constant (e.g., CURRENT_YEAR) at the top
of the test file and replace each repeated new Date().getFullYear() usage inside
the expected SPDX header strings with that constant (the locations shown are the
multi-line expected strings that start with "// SPDX-FileCopyrightText:
Copyright (c) ... NVIDIA CORPORATION & AFFILIATES. All rights reserved." and the
following "// SPDX-License-Identifier: Apache-2.0"); ensure CURRENT_YEAR is used
in every expected output block (the occurrences around the three other expected
blocks referenced) so the year value is centralized and consistent across tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: cba6324d-5dfa-49ff-8071-2d0bb7433fa8

📥 Commits

Reviewing files that changed from the base of the PR and between 52c89f0 and 9152c51.

📒 Files selected for processing (7)
  • projects/core/eslint.config.js
  • projects/internals/ci/notice/index.js
  • projects/internals/eslint/README.md
  • projects/internals/eslint/src/configs/css.js
  • projects/internals/eslint/src/configs/typescript.js
  • projects/internals/eslint/src/local/require-spdx-header.js
  • projects/internals/eslint/src/local/require-spdx-header.test.js

Comment thread projects/core/eslint.config.js Outdated
Comment thread projects/internals/eslint/src/local/require-spdx-header.js
Comment thread projects/internals/eslint/src/local/require-spdx-header.js
Comment thread projects/internals/eslint/src/local/require-spdx-header.test.js Outdated
@coryrylan coryrylan force-pushed the topic-lint-fixup-1 branch from 9152c51 to 8f52e6f Compare April 24, 2026 17:15
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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
projects/internals/eslint/src/local/require-spdx-header.js (1)

79-82: ⚠️ Potential issue | 🟡 Minor

Expand the replacement span across blank lines inside the SPDX block.

Line 80 only advances while the current line contains SPDX-, so a malformed header like copyright / blank line / identifier leaves the old identifier behind after --fix.

🩹 Suggested fix
   let headerEnd = headerStart;
-  while (headerEnd < lines.length && lines[headerEnd].includes('SPDX-')) {
-    headerEnd++;
-  }
+  let sawSpdx = false;
+  while (headerEnd < lines.length) {
+    const current = lines[headerEnd];
+    if (current.includes('SPDX-')) {
+      sawSpdx = true;
+      headerEnd++;
+      continue;
+    }
+    if (sawSpdx && current.trim() === '') {
+      headerEnd++;
+      continue;
+    }
+    break;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/internals/eslint/src/local/require-spdx-header.js` around lines 79 -
82, The loop that computes headerEnd only advances for lines that include
'SPDX-', so blank lines inside a malformed SPDX block are left out of the
replacement; update the loop in the block that uses headerStart/headerEnd/lines
so headerEnd increments while the current line is either blank
(lines[headerEnd].trim() === '') or contains 'SPDX-' (i.e., stop only when a
line is neither blank nor contains 'SPDX-'), ensuring the replacement span
covers SPDX identifiers separated by empty lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/internals/eslint/README.md`:
- Line 77: Update the README to document that the SPDX header rule is enforced
for CSS as well: add a bullet under the CSS section (or a short note) describing
the `local-css/require-spdx-header` rule and that it enforces the same two-line
SPDX header (`SPDX-FileCopyrightText` + `SPDX-License-Identifier: Apache-2.0`)
with year-handling behavior, and/or call out that `require-spdx-header` is
applied in both the TypeScript and CSS ESLint configs so readers know it's not
TypeScript-only.

In `@projects/internals/eslint/src/local/require-spdx-header.js`:
- Around line 112-119: extractYear currently scans the whole file and can pick
up SPDX text later in the file; change extractYear to only examine the top
header candidate by iterating from the start of lines and collecting the initial
contiguous header/comment block (stop when you hit the first blank line or a
line that is not a comment/header marker), then run the YEAR_PATTERN match
against that header block only; update references in the code that call
extractYear to pass the same lines array (no other API change required) and keep
using YEAR_PATTERN for the regex.

---

Duplicate comments:
In `@projects/internals/eslint/src/local/require-spdx-header.js`:
- Around line 79-82: The loop that computes headerEnd only advances for lines
that include 'SPDX-', so blank lines inside a malformed SPDX block are left out
of the replacement; update the loop in the block that uses
headerStart/headerEnd/lines so headerEnd increments while the current line is
either blank (lines[headerEnd].trim() === '') or contains 'SPDX-' (i.e., stop
only when a line is neither blank nor contains 'SPDX-'), ensuring the
replacement span covers SPDX identifiers separated by empty lines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: d1580d89-4921-4a76-abb4-d392287b8d55

📥 Commits

Reviewing files that changed from the base of the PR and between 9152c51 and 8f52e6f.

📒 Files selected for processing (7)
  • projects/core/eslint.config.js
  • projects/internals/ci/notice/index.js
  • projects/internals/eslint/README.md
  • projects/internals/eslint/src/configs/css.js
  • projects/internals/eslint/src/configs/typescript.js
  • projects/internals/eslint/src/local/require-spdx-header.js
  • projects/internals/eslint/src/local/require-spdx-header.test.js

Comment thread projects/internals/eslint/README.md
Comment on lines +112 to +119
function extractYear(lines) {
for (const line of lines) {
if (line.includes('SPDX-FileCopyrightText:')) {
const match = line.match(YEAR_PATTERN);
if (match) return match[1];
}
}
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Only preserve a year from the top header block.

extractYear() scans the entire file, so a later string or comment containing SPDX-FileCopyrightText can override the inserted year even when the file has no header at the top. Restrict this to the header candidate being rewritten, or stop once you leave the initial SPDX block.

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

In `@projects/internals/eslint/src/local/require-spdx-header.js` around lines 112
- 119, extractYear currently scans the whole file and can pick up SPDX text
later in the file; change extractYear to only examine the top header candidate
by iterating from the start of lines and collecting the initial contiguous
header/comment block (stop when you hit the first blank line or a line that is
not a comment/header marker), then run the YEAR_PATTERN match against that
header block only; update references in the code that call extractYear to pass
the same lines array (no other API change required) and keep using YEAR_PATTERN
for the regex.

Signed-off-by: Cory Rylan <crylan@nvidia.com>
@coryrylan coryrylan force-pushed the topic-lint-fixup-1 branch from 8f52e6f to 78c9bc2 Compare April 24, 2026 17:29
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.

Actionable comments posted: 1

♻️ Duplicate comments (3)
projects/internals/eslint/README.md (1)

77-77: ⚠️ Potential issue | 🟡 Minor

Document the CSS rule here too.

This addition makes require-spdx-header look TypeScript-only, but projects/internals/eslint/src/configs/css.js also enables local-css/require-spdx-header for src/**/*.css. Add the matching bullet under the CSS section or call out that the rule is enforced in both configs.

,

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

In `@projects/internals/eslint/README.md` at line 77, The README entry for
require-spdx-header is missing mention that the same rule is applied to CSS;
update the README.md to either add a bullet under the CSS section or note that
projects/internals/eslint/src/configs/css.js also enables
local-css/require-spdx-header for files matching src/**/*.css so readers know
the rule is enforced for both TypeScript and CSS files (reference the rule names
require-spdx-header and local-css/require-spdx-header and the CSS config file
projects/internals/eslint/src/configs/css.js).
projects/internals/eslint/src/local/require-spdx-header.js (2)

79-82: ⚠️ Potential issue | 🟡 Minor

Fixer may leave duplicate SPDX lines when header lines are split by blanks.

headerEnd stops at the first non-SPDX- line, so a header like copyright, blank line, identifier leaves the old identifier in place after fix.

🐛 Proposed fix
 function buildFixer(sourceCode, patterns, existingYear, headerStart) {
   const lines = sourceCode.lines;
   let headerEnd = headerStart;
-  while (headerEnd < lines.length && lines[headerEnd].includes('SPDX-')) {
-    headerEnd++;
-  }
+  let seenSpdx = false;
+  while (headerEnd < lines.length) {
+    const current = lines[headerEnd];
+    if (current.includes('SPDX-')) {
+      seenSpdx = true;
+      headerEnd++;
+      continue;
+    }
+    if (seenSpdx && current.trim() === '') {
+      headerEnd++;
+      continue;
+    }
+    break;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/internals/eslint/src/local/require-spdx-header.js` around lines 79 -
82, The loop that computes headerEnd in require-spdx-header.js stops at the
first non-SPDX line and thus leaves trailing SPDX identifiers when the header
contains blank lines; update the while condition that advances headerEnd (which
currently tests lines[headerEnd].includes('SPDX-')) to treat blank lines as part
of the header as well (e.g., continue advancing while the line includes 'SPDX-'
OR is empty/whitespace) so the fixer removes any old identifier lines even if
separated by blank lines; modify the logic around headerStart/headerEnd (and any
downstream slicing) to rely on this extended span.

112-120: ⚠️ Potential issue | 🟡 Minor

Only preserve a year from the top header block.

extractYear() scans the entire file, so a later string or comment containing SPDX-FileCopyrightText can override the inserted year even when the file has no header at the top. Restrict the search to the header candidate being rewritten:

🛡️ Proposed fix
-function extractYear(lines) {
-  for (const line of lines) {
+function extractYear(lines, headerStart) {
+  for (let i = headerStart; i < lines.length; i++) {
+    const line = lines[i];
+    if (!line.includes('SPDX-')) break;
     if (line.includes('SPDX-FileCopyrightText:')) {
       const match = line.match(YEAR_PATTERN);
       if (match) return match[1];
     }
   }
   return null;
 }

Then update the call site:

-  const existingYear = extractYear(lines);
+  const existingYear = extractYear(lines, headerStart);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/internals/eslint/src/local/require-spdx-header.js` around lines 112
- 120, extractYear currently scans the entire file and may pick up SPDX markers
outside the top header; change extractYear to only examine the header candidate
by restricting its input to the header block being rewritten (e.g., accept and
scan only headerLines or stop scanning once you leave the top comment block),
and update the call site that currently passes full file lines to instead pass
the header candidate lines (make sure to keep the existing YEAR_PATTERN and the
function name extractYear so locating is trivial).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/internals/eslint/src/local/require-spdx-header.test.js`:
- Around line 10-18: Consolidate repeated SPDX header strings by adding helper
functions (e.g., buildTsHeader(year, holder) and buildCssHeader(year, holder) or
a single buildSpdxHeader(kind, year, holder)) and replace the literals
referenced by VALID_TS, VALID_CSS and the other repeated fixtures mentioned
(lines using VALID_TS/VALID_CSS at the other test cases) to call these helpers;
ensure the helpers emit the exact comment style for TypeScript (//) vs CSS (/*
*/), include the SPDX-FileCopyrightText and SPDX-License-Identifier lines plus
the trailing blank line, and update all tests that currently inline headers (the
variables VALID_TS, VALID_CSS and the other duplicate header usages) to use the
centralized builder so the header format is maintained consistently.

---

Duplicate comments:
In `@projects/internals/eslint/README.md`:
- Line 77: The README entry for require-spdx-header is missing mention that the
same rule is applied to CSS; update the README.md to either add a bullet under
the CSS section or note that projects/internals/eslint/src/configs/css.js also
enables local-css/require-spdx-header for files matching src/**/*.css so readers
know the rule is enforced for both TypeScript and CSS files (reference the rule
names require-spdx-header and local-css/require-spdx-header and the CSS config
file projects/internals/eslint/src/configs/css.js).

In `@projects/internals/eslint/src/local/require-spdx-header.js`:
- Around line 79-82: The loop that computes headerEnd in require-spdx-header.js
stops at the first non-SPDX line and thus leaves trailing SPDX identifiers when
the header contains blank lines; update the while condition that advances
headerEnd (which currently tests lines[headerEnd].includes('SPDX-')) to treat
blank lines as part of the header as well (e.g., continue advancing while the
line includes 'SPDX-' OR is empty/whitespace) so the fixer removes any old
identifier lines even if separated by blank lines; modify the logic around
headerStart/headerEnd (and any downstream slicing) to rely on this extended
span.
- Around line 112-120: extractYear currently scans the entire file and may pick
up SPDX markers outside the top header; change extractYear to only examine the
header candidate by restricting its input to the header block being rewritten
(e.g., accept and scan only headerLines or stop scanning once you leave the top
comment block), and update the call site that currently passes full file lines
to instead pass the header candidate lines (make sure to keep the existing
YEAR_PATTERN and the function name extractYear so locating is trivial).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b7e79aa9-24f6-47d6-98cd-40f0ccb20ae2

📥 Commits

Reviewing files that changed from the base of the PR and between 8f52e6f and 78c9bc2.

📒 Files selected for processing (9)
  • projects/core/build/icons.js
  • projects/core/src/icon/icons.ts
  • projects/core/src/icon/server.ts
  • projects/internals/ci/notice/index.js
  • projects/internals/eslint/README.md
  • projects/internals/eslint/src/configs/css.js
  • projects/internals/eslint/src/configs/typescript.js
  • projects/internals/eslint/src/local/require-spdx-header.js
  • projects/internals/eslint/src/local/require-spdx-header.test.js

Comment thread projects/internals/eslint/src/local/require-spdx-header.test.js
@coryrylan coryrylan merged commit 2276640 into main Apr 24, 2026
8 checks passed
@coryrylan coryrylan deleted the topic-lint-fixup-1 branch April 24, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants