Skip to content

Commit 939601e

Browse files
Merge pull request #1 from mralaminahamed/chore/working-toolchain-and-docs
Make the toolchain work, and the docs match it
2 parents 3291755 + 7aaa3fe commit 939601e

7 files changed

Lines changed: 212 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: ci-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
check:
17+
runs-on: ubuntu-latest
18+
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
# The floor this package declares, and current stable. Consumers sit on both.
23+
php: ['7.4', '8.3']
24+
25+
steps:
26+
- uses: actions/checkout@v5
27+
28+
- uses: shivammathur/setup-php@v2
29+
with:
30+
php-version: ${{ matrix.php }}
31+
coverage: none
32+
33+
- uses: ramsey/composer-install@v3
34+
35+
- run: composer cs
36+
37+
- run: composer analyze
38+
39+
- run: composer test

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
/.idea/
55
/.vscode/
66
.DS_Store
7+
8+
# PHPUnit
9+
.phpunit.result.cache
10+
coverage/

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Changelog
2+
3+
All notable changes to this package are documented here.
4+
5+
**Versions track the upstream plugin.** A release of this package stubs the matching upstream
6+
release, so you can require the same version you run against. That is also why the entries below are
7+
mostly "regenerated against X" — the stubs have no behaviour of their own to change.
8+
9+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
10+
11+
## [Unreleased]
12+
13+
### Added
14+
15+
- `tests/StubsTest.php` and `phpunit.xml.dist`, so `composer test` runs. It asserts every generated
16+
stub file parses and actually declares something — the two ways a regeneration fails silently.
17+
- `phpcs.xml.dist`, so `composer cs` and `composer cs-fix` have a ruleset. Generated `.stub` files
18+
and `source/` are excluded; only the PHP written by hand here is checked.
19+
- CI on push and pull request across PHP 7.4 and 8.3, running `cs`, `analyze` and `test`.
20+
- This changelog.
21+
22+
### Fixed
23+
24+
- `composer test`, `composer cs` and `composer check` all failed: `phpunit` had no configuration and
25+
`phpcs` had no ruleset.
26+
- `composer analyze` reported errors that are inherent to stubs. The generated files were in
27+
PHPStan's `paths`, which asks it to check the bodies of declarations that are empty by definition.
28+
They are `scanFiles` now — the symbols become known without any claim about bodies a stub cannot
29+
have — and `paths` covers the hand-written PHP instead.
30+
31+
## [9.0.1] - 2026-07-31
32+
33+
Regenerated against upstream 9.0.1.

phpcs.xml.dist

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0"?>
2+
<ruleset name="woocommerce-subscriptions-stubs">
3+
<description>Coding standards for the files this repository writes by hand.</description>
4+
5+
<file>bin</file>
6+
<file>configs</file>
7+
<file>tests</file>
8+
9+
<!--
10+
The .stub files are generated by php-stubs/generator and rewritten wholesale by
11+
`composer generate`, so linting them would report on output nobody edits. source/ is the
12+
upstream copy, and vendor/ is not ours either.
13+
-->
14+
<exclude-pattern>*/source/*</exclude-pattern>
15+
<exclude-pattern>*/vendor/*</exclude-pattern>
16+
<exclude-pattern>*.stub</exclude-pattern>
17+
18+
<arg name="extensions" value="php"/>
19+
<arg name="colors"/>
20+
<arg value="ps"/>
21+
22+
<rule ref="PSR12"/>
23+
</ruleset>

phpstan.neon

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
parameters:
2+
level: 5
3+
4+
# The hand-written PHP, which is all this repository actually authors.
25
paths:
3-
- woocommerce-subscriptions-stubs.stub
6+
- configs
7+
- tests
8+
9+
# The stubs are SCANNED, not analysed.
10+
#
11+
# They used to sit in `paths`, which asks PHPStan to check the bodies of declarations that are
12+
# empty by definition -- "should return string but return statement is missing" for every one,
13+
# plus unknown-class errors for whatever WordPress internals upstream touches. None of it is a
14+
# defect in this package, and PHPStan 2.x classifies several of those as non-ignorable, so
15+
# neither an ignoreErrors pattern nor a baseline can hold them.
16+
#
17+
# Scanning makes the symbols known without asserting anything about their bodies, which is the
18+
# only claim a generated stub can honour. What the stubs are checked FOR -- that they parse, and
19+
# that they declare something -- is tests/StubsTest.php.
420
scanFiles:
521
- woocommerce-subscriptions-constants-stubs.stub
22+
- woocommerce-subscriptions-stubs.stub
623
bootstrapFiles:
724
- configs/bootstrap.php
8-
level: 5
9-
ignoreErrors:
10-
- '#but return statement is missing\.$#'
11-
- '#has an unused parameter#'
12-
- '#^(Property|Static property|Method|Static method) \S+ is unused\.$#'
13-
- '#is never read, only written\.$#'
14-
- '#has invalid (return )?type (WP_Error|WP_Customize_Manager|WP_Theme|WP_User|WP_Site|WP_Upgrader)#'

phpunit.xml.dist

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
failOnWarning="true"
7+
failOnRisky="true">
8+
<testsuites>
9+
<testsuite name="stubs">
10+
<directory suffix="Test.php">tests</directory>
11+
</testsuite>
12+
</testsuites>
13+
</phpunit>

tests/StubsTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WooCommerceSubscriptionsStubs\Tests;
6+
7+
use PHPUnit\Framework\TestCase;
8+
9+
/**
10+
* Smoke checks over the generated stubs.
11+
*
12+
* A stubs package fails quietly: a regeneration truncates the output, or produces a file that no
13+
* longer parses, and nothing here looks wrong until a consumer's static analysis reports symbols
14+
* that should exist. These assert the two properties a generated file must hold whatever it
15+
* contains -- it parses, and it actually declares something.
16+
*/
17+
final class StubsTest extends TestCase
18+
{
19+
private const ROOT = __DIR__ . '/..';
20+
21+
/**
22+
* Every generated stub file in the repository root.
23+
*
24+
* Discovered rather than listed, so a repository that grows a second stub file gets it covered
25+
* without anyone remembering to add it here.
26+
*
27+
* @return array<string, array{0: string}>
28+
*/
29+
public function stubFileProvider(): array
30+
{
31+
$cases = [];
32+
33+
foreach ((array) glob(self::ROOT . '/*.stub') as $path) {
34+
$cases[basename((string) $path)] = [basename((string) $path)];
35+
}
36+
37+
return $cases;
38+
}
39+
40+
public function testTheRepositoryShipsAtLeastOneStubFile(): void
41+
{
42+
$this->assertNotEmpty($this->stubFileProvider(), 'no .stub files found -- generation produced nothing');
43+
}
44+
45+
/**
46+
* @dataProvider stubFileProvider
47+
*/
48+
public function testTheStubFileIsValidPhp(string $file): void
49+
{
50+
$path = self::ROOT . '/' . $file;
51+
52+
$this->assertFileExists($path);
53+
54+
// TOKEN_PARSE makes the tokenizer raise ParseError on invalid source rather than returning a
55+
// best-effort token list, which is what makes this an actual syntax check.
56+
$this->assertNotEmpty(token_get_all((string) file_get_contents($path), TOKEN_PARSE));
57+
}
58+
59+
/**
60+
* @dataProvider stubFileProvider
61+
*/
62+
public function testTheStubFileDeclaresSomething(string $file): void
63+
{
64+
if (strpos($file, 'constants') !== false) {
65+
$this->markTestSkipped('a constants stub is legitimately empty when the plugin defines none');
66+
}
67+
68+
$source = (string) file_get_contents(self::ROOT . '/' . $file);
69+
70+
// A generated file that parses but contains only its header is the shape a truncated or
71+
// failed generation takes -- valid PHP, and useless.
72+
foreach (['function ', 'class ', 'interface ', 'trait ', 'define(', 'const '] as $needle) {
73+
if (stripos($source, $needle) !== false) {
74+
$this->addToAssertionCount(1);
75+
76+
return;
77+
}
78+
}
79+
80+
$this->fail($file . ' parses but declares nothing -- a truncated or empty generation');
81+
}
82+
}

0 commit comments

Comments
 (0)