Skip to content

staabm/phpstan-todo-by

Repository files navigation

phpstan-todo-by: comments with expiration

PHPStan extension to check for TODO/FIXME/XXX comments with expiration. Inspired by parker-codes/todo-by.

Examples

The main idea is, that comments within the source code will be turned into PHPStan errors when a condition is satisfied, e.g. a date reached, a version met, a issue tracker ticket is closed.

<?php

// TODO: 2023-12-14 This comment turns into a PHPStan error as of 14th december 2023
function doFoo() { /* ... */ }

// TODO https://github.com/staabm/phpstan-todo-by/issues/91 fix me when this GitHub issue is closed
class FooClass {}

// TODO: <1.0.0 This has to be in the first major release of this repo
function doBar() { /* ... */ }

// FIXME: phpunit/phpunit:5.3 This has to be fixed when updating phpunit to 5.3.x or higher
function doFooBar() { /* ... */ }

// XXX: php:8 drop this polyfill when php 8.x is required

// TODO: APP-2137 A comment which errors when the issue tracker ticket gets resolved
function doBaz() { /* ... */ }

Supported todo formats

A todo comment can also consist of just a constraint without any text, like // @todo 2023-12-14. When a text is given after the date, this text will be picked up for the PHPStan error message.

  • the todo, TODO, tOdO, FIXME, XXX keyword is case-insensitive
  • the todo keyword can be suffixed or prefixed by a @ character
  • a username might be included after the todo@
  • the comment might be mixed with : or - characters
  • multi line /* */ and /** */ comments are supported

Out of the box comments can expire by different constraints:

  • by date with format of YYYY-MM-DD matched against the reference-time
  • by a full github issue url
  • by a semantic version constraint matched against a Composer dependency (via composer.lock)

There are more builtin constraints, but these require additional configuration:

see examples of different comment variants which are supported:

// todo 2023-12-14
// @todo: 2023-12-14 fix it
// @todo 2023-12-14: fix it
// XXX - 2023-12-14 fix it
// FIXME 2023-12-14 - fix it

// TODO@staabm 2023-12-14 - fix it
// TODO@markus: 2023-12-14 - fix it

// TODO https://github.com/staabm/phpstan-todo-by/issues/91 fix me when this GitHub issue is closed

/*
 * other text
 *
 * @todo 2023-12-14 classic multi line comment
 *   more comment data
 */

// TODO: <1.0.0 This has to be in the first major release
// TODO >123.4: Must fix this or bump the version

// TODO: phpunit/phpunit:<5 This has to be fixed before updating to phpunit 5.x
// TODO@markus: phpunit/phpunit:5.3 This has to be fixed when updating phpunit to 5.3.x or higher

// TODO: APP-123 fix it when this Jira ticket is closed
// TODO: #123 fix it when this GitHub issue is closed
// TODO: GH-123 fix it when this GitHub issue is closed
// TODO: some-organization/some-repo#123 change me if this GitHub pull request is closed

Configuration

Non-ignorable errors

Errors emitted by the extension are non-ignorable by default, so they cannot accidentally be put into the baseline. You can change this behaviour with a configuration option within your phpstan.neon:

parameters:
    todo_by:
        nonIgnorable: false # default is true

Reference time

By default date-todo-comments are checked against todays date.

You might be interested, which comments will expire e.g. within the next 7 days, which can be configured with the referenceTime option. You need to configure a date parsable by strtotime.

parameters:
    todo_by:
        # any strtotime() compatible string
        referenceTime: "now+7days"

It can be especially handy to use a env variable for it, so you can pass the reference date e.g. via the CLI:

parameters:
    todo_by:
        referenceTime: %env.TODOBY_REF_TIME%

TODOBY_REF_TIME="now+7days" vendor/bin/phpstan analyze

Reference version

By default version-todo-comments are checked against "nextMajor" version. It is determined by fetching the latest local available git tag and incrementing the major version number.

The behaviour can be configured with the referenceVersion option. Possible values are "nextMajor", "nextMinor", "nextPatch" - which will be computed based on the latest local git tag - or any other version string like "1.2.3".

parameters:
    todo_by:
        # "nextMajor", "nextMinor", "nextPatch" or a version string like "1.2.3"
        referenceVersion: "nextMinor"

As shown in the "Reference time"-paragraph above, you might even use a env variable instead.

Note

The reference version is not applied to package-version-todo-comments which are matched against composer.lock instead.

Prerequisite

Make sure tags are available within your git clone, e.g. by running git fetch --tags origin - otherwise you are likely running into a 'Could not determine latest git tag' error.

In a GitHub Action this can be done like this:

    -   name: Checkout
        uses: actions/checkout@v4

    -   name: Get tags
        run: git fetch --tags origin

Multiple GIT repository support

By default the latest git tag to calculate the reference version is fetched once for all files beeing analyzed.

This behaviour can be configured with the singleGitRepo option.

In case you are using git submodules, or the analyzed codebase consists of multiple git repositories, set the singleGitRepo option to false which resolves the reference version for each directory beeing analyzed.

Virtual packages

Within the PHPStan config file you can define additional packages, to match against package-version-todo-comments.

parameters:
    todo_by:
        virtualPackages:
            'staabm/mypackage': '2.1.0'
            'staabm/my-api': '3.1.0'

Reference these virtual packages like any other package in your todo-comments:

// TODO staabm/mypackage:2.2.0 remove the following function once staabm/mypackage is updated to 2.2.0

Issue tracker key support

Optionally you can configure this extension to analyze your comments with issue tracker ticket keys. The extension fetches issue tracker API for issue status. If the remote issue is resolved, the comment will be reported.

Currently, Jira, GitHub and YouTrack are supported.

This feature is disabled by default. To enable it, you must set ticket.enabled parameter to true. You also need to set these parameters:

parameters:
    todo_by:
        ticket:
            enabled: true

            # one of: jira, github (case-sensitive)
            tracker: jira

            # a case-sensitive list of status names.
            # only tickets having any of these statuses are considered resolved.
            # supported trackers: jira. Other trackers ignore this parameter.
            resolvedStatuses:
                - Done
                - Resolved
                - Declined

            # if your ticket key is FOO-12345, then this value should be ["FOO"].
            # multiple key prefixes are allowed, e.g. ["FOO", "APP"].
            # only comments with keys containing this prefix will be analyzed.
            # supported trackers: jira, youtrack. Other trackers ignore this parameter.
            keyPrefixes:
                - FOO

            jira:
                # e.g. https://your-company.atlassian.net
                server: https://acme.atlassian.net

                # see below for possible formats.
                # if this value is empty, credentials file will be used instead.
                credentials: %env.JIRA_TOKEN%

                # path to a file containing Jira credentials.
                # see below for possible formats.
                # if credentials parameter is not empty, it will be used instead of this file.
                # this file must not be committed into the repository!
                credentialsFilePath: .secrets/jira-credentials.txt

            github:
                # The account owner of referenced repositories.
                defaultOwner: your-name

                # The name of the repository without the .git extension.
                defaultRepo: your-repository

                # GitHub Access Token
                # if this value is empty, credentials file will be used instead.
                credentials: null

                # path to a file containing GitHub Access Token.
                # if credentials parameter is not empty, it will be used instead of this file.
                # this file must not be committed into the repository!
                credentialsFilePath: null

            youtrack:
                # e.g. https://your-company.youtrack.cloud
                server: https://acme.youtrack.cloud

                # YouTrack permanent token
                # if this value is empty, credentials file will be used instead.
                credentials: %env.YOUTRACK_TOKEN%

                # path to a file containing a YouTrack permanent token
                # if credentials parameter is not empty, it will be used instead of this file.
                # this file must not be committed into the repository!
                credentialsFilePath: .secrets/youtrack-credentials.txt

Jira Credentials

This extension uses Jira's REST API to fetch ticket's status. If your board is not public, you need to configure valid credentials. These authentication methods are supported:

We recommend you use OAuth over basic authentication, especially if you use phpstan in CI. There are multiple ways to pass your credentials to this extension. You should choose one of them - if you define both parameters, only credentials parameter is considered and the file is ignored.

Pass credentials in environment variable

Configure credentials parameter to read value from environment variable:

parameters:
    todo_by:
        ticket:
            jira:
                credentials: %env.JIRA_TOKEN%

Depending on chosen authentication method its content should be:

  • Access Token for token based authentication, e.g. JIRA_TOKEN=ATATT3xFfGF0Gv_pLFSsunmigz8wq5Y0wkogoTMgxDFHyR...
  • <username>:<passwordOrApiKey> for basic authentication, e.g. [email protected]:p@ssword
Pass credentials in text file

Create text file in your project's directory (or anywhere else on your computer) and put its path into configuration:

parameters:
    todo_by:
        ticket:
            jira:
                credentialsFilePath: path/to/file

Remember not to commit this file to repository! Depending on chosen authentication method its value should be:

  • Access Token for token based authentication, e.g. JATATT3xFfGF0Gv_pLFSsunmigz8wq5Y0wkogoTMgxDFHyR...
  • <username>:<passwordOrApiKey> for basic authentication, e.g. [email protected]:p@ssword

GitHub

Both issues and pull requests are supported. The comment will be reported if the referenced issue/PR is closed. There are multiple ways to reference GitHub issue/PR:

Only number
// TODO: #123 - fix me
// TODO: GH-123 - fix me

If the defaultOwner is set to acme and defaultRepo is set to hello-world, the referenced issue is resolved to acme/hello-world#123.

Owner + repository name + number
// TODO: acme/hello-world#123 - fix me

Installation

To use this extension, require it in Composer:

composer require --dev staabm/phpstan-todo-by

If you also install phpstan/extension-installer then you're all set!

Manual installation

If you don't want to use phpstan/extension-installer, include extension.neon in your project's PHPStan config:

includes:
    - vendor/staabm/phpstan-todo-by/extension.neon

FAQ

Unexpected '"php" version requirement ">=XXX" satisfied' error

If you get this errors too early, it might be caused by wrong version constraints in your composer.json file. A php version constraint of e.g. ^7.4 means >=7.4.0 && <= 7.999999.99999, which means comments like // TODO >7.5 will emit an error.

For the php declaration, it is recommended to use a version constraint with a fixed upper bound, e.g. 7.4.* or ^7 || <8.3.

'Could not determine latest git tag' error

This error is thrown, when no git tags are available within your git clone. Fetch git tags, as described in the "Reference version"-chapter above.

πŸ’Œ Give back some love

Consider supporting the project, so we can make this tool even better even faster for everyone.