Skip to content

feat: add removeClassAttribute method for class attributes#58

Open
artengin wants to merge 10 commits into
masterfrom
feat-remove-class-attribute-method
Open

feat: add removeClassAttribute method for class attributes#58
artengin wants to merge 10 commits into
masterfrom
feat-remove-class-attribute-method

Conversation

@artengin
Copy link
Copy Markdown
Contributor

@artengin artengin commented Apr 8, 2026

refs: #57

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 8, 2026

Coverage Report for CI Build 25501877436

Coverage increased (+0.06%) to 98.615%

Details

  • Coverage increased (+0.06%) from the base build.
  • Patch coverage: 14 of 14 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 361
Covered Lines: 356
Line Coverage: 98.61%
Coverage Strength: 9.74 hits per line

💛 - Coveralls

@DenTray
Copy link
Copy Markdown
Collaborator

DenTray commented Apr 9, 2026

@codex review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for removing PHP 8 attributes from a specific class declaration via PHPFileBuilder, including coverage for grouped (#[A, B]) and repeated attributes, plus a small refactor of the visitor base class to centralize “parent node not found” handling.

Changes:

  • Introduce RemoveClassAttribute visitor and expose it via PHPFileBuilder::removeClassAttribute().
  • Move hasParentNode + afterTraverse() “parent node not found” hook into BaseNodeVisitorAbstract (simplifying InsertOrUpdateNodeAbstractVisitor).
  • Add PHPUnit cases + fixtures for removal, grouped attributes, no-op when attribute missing, and error scenarios.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Builders/PHPFileBuilder.php Adds removeClassAttribute() API to attach the new visitor.
src/Visitors/RemoveClassAttribute.php Implements class-level attribute removal logic.
src/Visitors/BaseNodeVisitorAbstract.php Centralizes hasParentNode and the post-traverse hook.
src/Visitors/InsertOrUpdateNodeAbstractVisitor.php Removes duplicated hasParentNode/afterTraverse() logic now handled in the base class.
tests/PHPFileBuilderTest.php Adds test coverage for removeClassAttribute behavior and errors.
tests/fixtures/PHPFileBuilderTest/original/class_with_attributes.php New input fixture for class-level attributes + member attributes.
tests/fixtures/PHPFileBuilderTest/original/class_with_grouped_attributes.php New input fixture for #[A, B] grouped attributes.
tests/fixtures/PHPFileBuilderTest/original/class_with_multiple_same_attributes.php New input fixture for repeated same-name class attributes.
tests/fixtures/PHPFileBuilderTest/results/class_without_attributes.php Expected output after removing class-level attributes.
tests/fixtures/PHPFileBuilderTest/results/class_with_grouped_attributes_one_removed.php Expected output after removing one attribute from a grouped block.
tests/fixtures/PHPFileBuilderTest/results/class_with_attributes_unchanged.php Expected output for the no-op (no matching attribute).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +39 to +44
protected function validateClassName(Class_ $node): void
{
if ($this->className !== $node->name->name) {
throw new NodeNotExistException('Class', $this->className);
}
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

validateClassName() throws NodeNotExistException as soon as it encounters a Class_ node whose name doesn’t match the target. If the file contains multiple classes, this will abort on the first non-target class even if the requested class exists later in the AST. Consider instead tracking a hasTargetClass flag: on non-matching classes, just return without changes; on match, set the flag and perform removal; then in afterTraverse() throw NodeNotExistException('Class', $className) only if no matching class was ever found (while still using parentNodeNotFoundHook() for the “no class nodes at all” case).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Multiple classes in a single file violates PSR-1

@artengin artengin assigned artengin and unassigned DenTray Apr 14, 2026
@artengin
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb2719b287

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +35 to +36
if ($this->className !== $node->name->name) {
throw new NodeNotExistException('Class', $this->className);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Defer class-not-found error until traversal finishes

validateClassName() throws on the first Class_ node whose name differs, so removeClassAttribute() fails in files that contain any additional class node (for example, a second top-level class or an anonymous class encountered before/after the target) even when the requested $className is present. In the anonymous-class case, $node->name is null, so $node->name->name can also crash at runtime. The visitor should track whether a matching named class was found and only throw NodeNotExistException after traversal if none matched.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Multiple classes in a single file violates PSR-1

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c77fe6b11

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


protected function validateClassName(Class_ $node): void
{
if ($this->className !== $node->name->name) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Ignore anonymous classes when validating class name

Fresh evidence: even in a PSR-1 single-class file, a nested new class {} creates a Class_ node with no name, and this comparison dereferences $node->name unconditionally. When removeClassAttribute() traverses such a file, it fails before reaching the intended class (or throws NodeNotExistException on the wrong node), so attribute removal is impossible for valid PHP files that include anonymous classes.

Useful? React with 👍 / 👎.

@artengin artengin assigned DenTray and unassigned artengin May 7, 2026
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.

4 participants