Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
GH Actions/publish-wiki: auto-generate table of contents
This commit adds steps to the workflow to auto-generate a GitHub wiki compatible table of contents in most wiki pages.

This reduces the risk of a TOC being out-of-date or containing incorrectly formatted links, as well as reduces the maintenance burden.

This action uses the `doctoc` pages to generate the table of contents and this can be tested locally using the same steps as used in the GH Actions workflow:
```
npm install -g doctoc
cp -v -a wiki _wiki
doctoc ./_wiki/ --github --maxlevel 4 --update-only
doctoc ./_wiki/Version-4.0-User-Upgrade-Guide.md --github --maxlevel 3 --update-only
```

Notes:
* The files are copied to a `_wiki` directory - which is `.gitignore`d - before pre-processing to reduce the risk of the source files being accidentally updated (and committed), which would undo the automation.
* The `--github` flag puts the TOC generation in GitHub compatible mode.
* The `--update-only` flag means that only markdown files containing the `<!-- START doctoc -->` and `<!-- END doctoc -->` markers will be updated and files without those markers will be left alone.
* By default, the TOC will contain all headers up to the indicated `--maxlevel`.
    For the V 4.0 Dev upgrade guide, this looked weird, what with some "Upgrading" headers being at level 4 and some at level 5.
    To mitigate this, a couple of headers have been turned into "bold phrases" instead.
    Along the same lines, for the V 4.0 User upgrade guide, the level 4 headers were always "Upgrading". Those belong with their parent heading and IMO do not need to be separately called out in the TOC, which explains the second call to `doctoc` to overrule the TOC for that file specifically with a `--maxlevel 3` setting.
* The start/end markers have been added to all files which contained a TOC, except for one: `Reporting.md`.
    The reason for this exception is that the section order in the file does not match the current TOC order, with the existing TOC order making sense from a TOC point of view, while the section order makes sense from a "types of reports most used" point of view.
    Whether this page should be re-organized or not, is outside the scope of this PR.

To allow contributors to review the resulting pre-processed wiki files, the files are uploaded as an artifact when a PR dry-run is being executed and a comment is posted on the PR requesting the contributor to review the pre-processed files.

Includes adding a TOC to the Coding Standards Tutorial page and adding "back to top" links within the page.

Ref:
* https://github.com/thlorenz/doctoc
  • Loading branch information
jrfnl committed Jul 14, 2025
commit 58762377351dd578379eabe0f0d0fa1ac4459fc8
53 changes: 53 additions & 0 deletions .github/workflows/publish-wiki.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,31 @@ jobs:
permissions:
# Needed for the commit to the wiki.
contents: write
# Needed for the PR comment.
pull-requests: write

steps:
- name: Checkout code
uses: actions/checkout@v4


# ################################################################################
# Update Wiki files.
# ################################################################################

- name: Install DocToc table of contents generator
run: npm install -g doctoc

- name: Copy wiki files to temporary location
shell: bash
run: cp -v -a wiki _wiki

- name: Update tables of contents
run: doctoc ./_wiki/ --github --maxlevel 4 --update-only

- name: Re-run tables of contents with different settings for specific file
run: doctoc ./_wiki/Version-4.0-User-Upgrade-Guide.md --github --maxlevel 3 --update-only

- name: Preface markdown files with warning not to edit in place
shell: bash
# yamllint disable rule:line-length
Expand All @@ -50,6 +66,43 @@ jobs:
'1i\<!--\nWARNING: DO NOT EDIT THIS FILE IN THE WIKI.\nThis wiki is updated from the https://github.com/PHPCSStandards/PHP_CodeSniffer-documentation repository.\nSubmit a PR to that repository updating the relevant file in the /wiki/ subdirectory instead.\n-->\n' {} \;
# yamllint enable rule:line-length


# ################################################################################
# Dry-run/PRs: upload artifact with pre-processed files and post comment in PR.
# ################################################################################

# Retention is normally 90 days, but this artifact is only to help with reviewing PRs,
# especially when new output blocks are added or the (workflow) code for existing ones
# is updated. All in all, no need to keep the artifact for more than a few days.
- name: "[PR only] Upload the preprocessed wiki files as an artifact"
if: ${{ github.event_name == 'pull_request' }}
id: artifact
uses: actions/upload-artifact@v4
with:
name: wiki-files
path: ./_wiki
if-no-files-found: error
retention-days: 10

- name: "[PR only] Post comment to review artifact"
if: ${{ github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@v2
with:
repo-token: ${{ secrets.COMMENT_ON_PRS_TOKEN }}
message: |
Thank you for your PR.
A dry-run has been executed on your PR, executing all markdown pre-processing for the wiki files.

Please review the resulting final markdown files via the [created artifact](${{ steps.artifact.outputs.artifact-url }}).
This is especially important when adding new pages.

_N.B.: the above link will automatically be updated when this PR is updated._


# ################################################################################
# Deploy to the wiki in the PHPCS repo.
# ################################################################################

- name: Check GitHub Git Operations status
uses: crazy-max/ghaction-github-status@v4
with:
Expand Down
12 changes: 2 additions & 10 deletions wiki/About-Standards-for-PHP_CodeSniffer.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
## Table of contents

* [A Project ruleset or a standard ?](#a-project-ruleset-or-a-standard-)
* [How does PHP_CodeSniffer determine which standard or ruleset to apply ?](#how-does-php_codesniffer-determine-which-standard-or-ruleset-to-apply-)
* [About standards](#about-standards)
* [Creating an external standard for PHP_CodeSniffer](#creating-an-external-standard-for-php_codesniffer)
* [Creating new rules](#creating-new-rules)
* [Naming conventions](#naming-conventions)
* [1. Directory structure](#1-directory-structure)
* [2. Sniff file name](#2-sniff-file-name)
* [3. Namespace and class name](#3-namespace-and-class-name)
* [Examples](#examples)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
21 changes: 2 additions & 19 deletions wiki/Advanced-Usage.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
## Table of contents

* [Specifying Valid File Extensions](#specifying-valid-file-extensions)
* [Ignoring Files and Folders](#ignoring-files-and-folders)
* [Ignoring Parts of a File](#ignoring-parts-of-a-file)
* [Limiting Results to Specific Sniffs](#limiting-results-to-specific-sniffs)
* [Filtering Errors and Warnings Based on Severity](#filtering-errors-and-warnings-based-on-severity)
* [Replacing Tabs with Spaces](#replacing-tabs-with-spaces)
* [Specifying an Encoding](#specifying-an-encoding)
* [Using a Bootstrap File](#using-a-bootstrap-file)
* [Using a Default Configuration File](#using-a-default-configuration-file)
* [Specifying php.ini Settings](#specifying-phpini-settings)
* [Setting Configuration Options](#setting-configuration-options)
* [Deleting Configuration Options](#deleting-configuration-options)
* [Viewing Configuration Options](#viewing-configuration-options)
* [Printing Verbose Tokeniser Output](#printing-verbose-tokeniser-output)
* [The Scope Map](#the-scope-map)
* [The Level Map](#the-level-map)
* [Printing Verbose Token Processing Output](#printing-verbose-token-processing-output)
* [Quieting Output](#quieting-output)
* [Understanding the Exit Codes](#understanding-the-exit-codes)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
26 changes: 26 additions & 0 deletions wiki/Coding-Standard-Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ In this tutorial, we will create a new coding standard with a single sniff. Our

Sniffs need to follow [strict directory layout and naming conventions](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/About-Standards-for-PHP_CodeSniffer#naming-conventions).

## Table of contents

<!-- START doctoc -->
<!-- END doctoc -->

***

## Creating the Coding Standard Directory

All sniffs in PHP_CodeSniffer must belong to a coding standard. A coding standard is a directory with a specific sub-directory structure and a `ruleset.xml` file, so creating a standard is straight-forward.
Expand Down Expand Up @@ -39,6 +46,9 @@ The content of the `ruleset.xml` file should, at a minimum, be the following:
> [!NOTE]
> The ruleset.xml can be left quite small, as it is in this example coding standard. For information about the other features that the `ruleset.xml` provides, see the [[Annotated ruleset]].

<p align="right"><a href="#table-of-contents">back to top</a></p>


## Creating the Sniff

A sniff requires a single PHP file that must be placed into a sub-directory to categorise the type of check it performs. Its name should clearly describe the standard that we are enforcing and must end with `Sniff.php`. For our sniff, we will name the PHP file `DisallowHashCommentsSniff.php` and place it into a `Commenting` sub-directory to categorise this sniff as relating to commenting. Run the following commands to create the category and the sniff:
Expand All @@ -54,24 +64,36 @@ $ touch Commenting/DisallowHashCommentsSniff.php

Each sniff must implement the `PHP_CodeSniffer\Sniffs\Sniff` interface so that PHP_CodeSniffer knows that it should instantiate the sniff once it's invoked. The interface defines two methods that must be implemented; `register` and `process`.

<p align="right"><a href="#table-of-contents">back to top</a></p>


## The `register` and `process` Methods

The `register` method allows a sniff to subscribe to one or more token types that it wants to process. Once PHP_CodeSniffer encounters one of those tokens, it calls the `process` method with the `PHP_CodeSniffer\Files\File` object (a representation of the current file being checked) and the position in the stack where the token was found.

For our sniff, we are interested in single line comments. The `token_get_all` method that PHP_CodeSniffer uses to acquire the tokens within a file distinguishes doc comments and normal comments as two separate token types. Therefore, we don't have to worry about doc comments interfering with our test. The `register` method only needs to return one token type, `T_COMMENT`.

<p align="right"><a href="#table-of-contents">back to top</a></p>


## The Token Stack

A sniff can gather more information about a token by acquiring the token stack with a call to the `getTokens` method on the `PHP_CodeSniffer\Files\File` object. This method returns an array and is indexed by the position where the token occurs in the token stack. Each element in the array represents a token. All tokens have a `code`, `type` and a `content` index in their array. The `code` value is a unique integer for the type of token. The `type` value is a string representation of the token (e.g., `'T_COMMENT'` for comment tokens). The `type` has a corresponding globally defined integer with the same name. Finally, the `content` value contains the content of the token as it appears in the code.

> [!NOTE]
> Depending on the token, the token array may contain various additional indexes with further information on a token.

<p align="right"><a href="#table-of-contents">back to top</a></p>


## Reporting Errors

Once an error is detected, a sniff should indicate that an error has occurred by calling the `addError` method on the `PHP_CodeSniffer\Files\File` object, passing in an appropriate error message as the first argument, the position in the stack where the error was detected as the second, a code to uniquely identify the error within this sniff and an array of data used inside the error message.
Alternatively, if the violation is considered not as critical as an error, the `addWarning` method can be used.

<p align="right"><a href="#table-of-contents">back to top</a></p>


## DisallowHashCommentsSniff.php

We now have to write the content of our sniff. The content of the `DisallowHashCommentsSniff.php` file should be the following:
Expand Down Expand Up @@ -135,6 +157,8 @@ public $supportedTokenizers = [
];
```

<p align="right"><a href="#table-of-contents">back to top</a></p>


## Results

Expand Down Expand Up @@ -173,3 +197,5 @@ FOUND 3 ERROR(S) AFFECTING 3 LINE(S)
13 | ERROR | Hash comments are prohibited; found # Error.
--------------------------------------------------------------------------------
```

<p align="right"><a href="#table-of-contents">back to top</a></p>
24 changes: 2 additions & 22 deletions wiki/Configuration-Options.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
## Table of contents

* [Setting the default coding standard](#setting-the-default-coding-standard)
* [Setting the default report format](#setting-the-default-report-format)
* [Hiding warnings by default](#hiding-warnings-by-default)
* [Showing progress by default](#showing-progress-by-default)
* [Using colors in output by default](#using-colors-in-output-by-default)
* [Changing the default severity levels](#changing-the-default-severity-levels)
* [Setting the default report width](#setting-the-default-report-width)
* [Setting the default encoding](#setting-the-default-encoding)
* [Setting the default tab width](#setting-the-default-tab-width)
* [Setting the installed standard paths](#setting-the-installed-standard-paths)
* [Setting the PHP version](#setting-the-php-version)
* [Ignoring errors when generating the exit code](#ignoring-errors-when-generating-the-exit-code)
* [Ignoring warnings when generating the exit code](#ignoring-warnings-when-generating-the-exit-code)
* [Ignoring non-auto-fixable issues when generating the exit code (PHP_CodeSniffer >= 4.0.0)](#ignoring-non-auto-fixable-issues-when-generating-the-exit-code-php_codesniffer--400)
* Setting tool paths
* [CSSLint](#setting-the-path-to-csslint)
* [Google Closure Linter](#setting-the-path-to-the-google-closure-linter)
* [PHP](#setting-the-path-to-php)
* [JSHint](#setting-the-path-to-jshint)
* [JSLint](#setting-the-path-to-jslint)
* [JavaScript Lint](#setting-the-path-to-javascript-lint)
* [Zend Code Analyzer](#setting-the-path-to-the-zend-code-analyzer)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
66 changes: 2 additions & 64 deletions wiki/Customisable-Sniff-Properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,8 @@ For more information about changing sniff behaviour by customising your ruleset,

## Table of contents

* [Generic Sniffs](#generic-sniffs)
* [Generic.Arrays.ArrayIndent](#genericarraysarrayindent)
* [Generic.CodeAnalysis.UnusedFunctionParameter](#genericcodeanalysisunusedfunctionparameter)
* [Generic.ControlStructures.InlineControlStructure](#genericcontrolstructuresinlinecontrolstructure)
* [Generic.Debug.ClosureLinter](#genericdebugclosurelinter)
* [Generic.Debug.ESLint](#genericdebugeslint)
* [Generic.Files.LineEndings](#genericfileslineendings)
* [Generic.Files.LineLength](#genericfileslinelength)
* [Generic.Formatting.MultipleStatementAlignment](#genericformattingmultiplestatementalignment)
* [Generic.Formatting.SpaceAfterCast](#genericformattingspaceaftercast)
* [Generic.Formatting.SpaceAfterNot](#genericformattingspaceafternot)
* [Generic.Functions.OpeningFunctionBraceBsdAllman](#genericfunctionsopeningfunctionbracebsdallman)
* [Generic.Functions.OpeningFunctionBraceKernighanRitchie](#genericfunctionsopeningfunctionbracekernighanritchie)
* [Generic.Metrics.CyclomaticComplexity](#genericmetricscyclomaticcomplexity)
* [Generic.Metrics.NestingLevel](#genericmetricsnestinglevel)
* [Generic.NamingConventions.CamelCapsFunctionName](#genericnamingconventionscamelcapsfunctionname)
* [Generic.PHP.ForbiddenFunctions](#genericphpforbiddenfunctions)
* [Generic.PHP.NoSilencedErrors](#genericphpnosilencederrors)
* [Generic.Strings.UnnecessaryStringConcat](#genericstringsunnecessarystringconcat)
* [Generic.WhiteSpace.ArbitraryParenthesesSpacing](#genericwhitespacearbitraryparenthesesspacing)
* [Generic.WhiteSpace.ScopeIndent](#genericwhitespacescopeindent)
* [Generic.WhiteSpace.SpreadOperatorSpacingAfter](#genericwhitespacespreadoperatorspacingafter)
* [PEAR Sniffs](#pear-sniffs)
* [PEAR.Commenting.FunctionComment](#pearcommentingfunctioncomment)
* [PEAR.ControlStructures.ControlSignature](#pearcontrolstructurescontrolsignature)
* [PEAR.ControlStructures.MultiLineCondition](#pearcontrolstructuresmultilinecondition)
* [PEAR.Formatting.MultiLineAssignment](#pearformattingmultilineassignment)
* [PEAR.Functions.FunctionCallSignature](#pearfunctionsfunctioncallsignature)
* [PEAR.Functions.FunctionDeclaration](#pearfunctionsfunctiondeclaration)
* [PEAR.WhiteSpace.ObjectOperatorIndent](#pearwhitespaceobjectoperatorindent)
* [PEAR.WhiteSpace.ScopeClosingBrace](#pearwhitespacescopeclosingbrace)
* [PEAR.WhiteSpace.ScopeIndent](#pearwhitespacescopeindent)
* [PSR2 Sniffs](#psr2-sniffs)
* [PSR2.Classes.ClassDeclaration](#psr2classesclassdeclaration)
* [PSR2.ControlStructures.ControlStructureSpacing](#psr2controlstructurescontrolstructurespacing)
* [PSR2.ControlStructures.SwitchDeclaration](#psr2controlstructuresswitchdeclaration)
* [PSR2.Methods.FunctionCallSignature](#psr2methodsfunctioncallsignature)
* [PSR12 Sniffs](#psr12-sniffs)
* [PSR12.Classes.AnonClassDeclaration](#psr12classesanonclassdeclaration)
* [PSR12.ControlStructures.BooleanOperatorPlacement](#psr12controlstructuresbooleanoperatorplacement)
* [PSR12.ControlStructures.ControlStructureSpacing](#psr12controlstructurescontrolstructurespacing)
* [PSR12.Namespaces.CompoundNamespaceDepth](#psr12namespacescompoundnamespacedepth)
* [PSR12.Operators.OperatorSpacing](#psr12operatorsoperatorspacing)
* [Squiz Sniffs](#squiz-sniffs)
* [Squiz.Classes.ClassDeclaration](#squizclassesclassdeclaration)
* [Squiz.Commenting.FunctionComment](#squizcommentingfunctioncomment)
* [Squiz.Commenting.LongConditionClosingComment](#squizcommentinglongconditionclosingcomment)
* [Squiz.ControlStructures.ControlSignature](#squizcontrolstructurescontrolsignature)
* [Squiz.ControlStructures.ForEachLoopDeclaration](#squizcontrolstructuresforeachloopdeclaration)
* [Squiz.ControlStructures.ForLoopDeclaration](#squizcontrolstructuresforloopdeclaration)
* [Squiz.ControlStructures.SwitchDeclaration](#squizcontrolstructuresswitchdeclaration)
* [Squiz.CSS.ForbiddenStyles](#squizcssforbiddenstyles)
* [Squiz.CSS.Indentation](#squizcssindentation)
* [Squiz.Functions.FunctionDeclaration](#squizfunctionsfunctiondeclaration)
* [Squiz.Functions.FunctionDeclarationArgumentSpacing](#squizfunctionsfunctiondeclarationargumentspacing)
* [Squiz.PHP.CommentedOutCode](#squizphpcommentedoutcode)
* [Squiz.PHP.DiscouragedFunctions](#squizphpdiscouragedfunctions)
* [Squiz.PHP.ForbiddenFunctions](#squizphpforbiddenfunctions)
* [Squiz.Strings.ConcatenationSpacing](#squizstringsconcatenationspacing)
* [Squiz.WhiteSpace.FunctionSpacing](#squizwhitespacefunctionspacing)
* [Squiz.WhiteSpace.MemberVarSpacing](#squizwhitespacemembervarspacing)
* [Squiz.WhiteSpace.ObjectOperatorSpacing](#squizwhitespaceobjectoperatorspacing)
* [Squiz.WhiteSpace.OperatorSpacing](#squizwhitespaceoperatorspacing)
* [Squiz.WhiteSpace.SuperfluousWhitespace](#squizwhitespacesuperfluouswhitespace)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
8 changes: 2 additions & 6 deletions wiki/FAQ.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
## Table of contents

* [Does PHP_CodeSniffer perform any code coverage or unit testing?](#does-php_codesniffer-perform-any-code-coverage-or-unit-testing)
* [My code is fine! Why do I need PHP_CodeSniffer?](#my-code-is-fine-why-do-i-need-php_codesniffer)
* [Does PHP_CodeSniffer parse my code to ensure it will execute?](#does-php_codesniffer-parse-my-code-to-ensure-it-will-execute)
* [I don't agree with your coding standards! Can I make PHP_CodeSniffer enforce my own?](#i-dont-agree-with-your-coding-standards-can-i-make-php_codesniffer-enforce-my-own)
* [How come PHP_CodeSniffer reported errors, I fixed them, now I get even more?](#how-come-php_codesniffer-reported-errors-i-fixed-them-now-i-get-even-more)
* [What does PHP_CodeSniffer use to tokenize my code?](#what-does-php_codesniffer-use-to-tokenize-my-code)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
5 changes: 2 additions & 3 deletions wiki/Fixing-Errors-Automatically.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
## Table of contents

* [About Automatic Fixing](#about-automatic-fixing)
* [Using the PHP Code Beautifier and Fixer](#using-the-php-code-beautifier-and-fixer)
* [Viewing Debug Information](#viewing-debug-information)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
9 changes: 2 additions & 7 deletions wiki/Usage.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
## Table of contents

* [Getting Help from the Command Line](#getting-help-from-the-command-line)
* [Checking Files and Folders](#checking-files-and-folders)
* [Printing a Summary Report](#printing-a-summary-report)
* [Printing Progress Information](#printing-progress-information)
* [Specifying a Coding Standard](#specifying-a-coding-standard)
* [Printing a List of Installed Coding Standards](#printing-a-list-of-installed-coding-standards)
* [Listing Sniffs Inside a Coding Standard](#listing-sniffs-inside-a-coding-standard)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
16 changes: 2 additions & 14 deletions wiki/Version-3.0-Upgrade-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,8 @@ PHP_CodeSniffer version 3 contains a large number of core changes and breaks bac

## Table of contents

* [Upgrading Custom Sniffs](#upgrading-custom-sniffs)
* [Extending Other Sniffs](#extending-other-sniffs)
* [Extending the Included Abstract Sniffs](#extending-the-included-abstract-sniffs)
* [AbstractVariableSniff](#abstractvariablesniff)
* [AbstractPatternSniff](#abstractpatternsniff)
* [AbstractScopeSniff](#abstractscopesniff)
* [New Class Names](#new-class-names)
* [PHP_CodeSniffer_File](#php_codesniffer_file)
* [PHP_CodeSniffer_Tokens](#php_codesniffer_tokens)
* [PHP_CodeSniffer](#php_codesniffer)
* [Upgrading Unit Tests](#upgrading-unit-tests)
* [Setting CLI Values](#setting-cli-values)
* [Upgrading Custom Reports](#upgrading-custom-reports)
* [Supporting Concurrency](#supporting-concurrency)
<!-- START doctoc -->
<!-- END doctoc -->

***

Expand Down
Loading