oliverthiele/typo3-fluid-linter

A linter for TYPO3 Fluid templates — catches typographic quotes, deprecated syntax, and Fluid 5 breaking changes

Maintainers

Package info

github.com/oliverthiele/typo3-fluid-linter

pkg:composer/oliverthiele/typo3-fluid-linter

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.7.0 2026-07-02 10:27 UTC

This package is auto-updated.

Last update: 2026-07-02 11:27:51 UTC


README

A CLI linter for TYPO3 Fluid templates that catches encoding errors, deprecated syntax, and Fluid 5 breaking changes before they reach production.

Packagist Version PHP License Changelog

Why TYPO3 Fluid Linter?

Existing Fluid linters (Fluid.Lint, fluid-lint) take an AST-based approach: they parse templates using the Fluid engine itself, which requires typo3fluid/fluid as a dependency and catches structural syntax errors. This tool takes a different approach — regex-based, zero dependency, no TYPO3 required — and targets a different class of problems: encoding artifacts from AI-assisted development, deprecated ViewHelper usage across TYPO3 versions, and Fluid 5 breaking changes. Both approaches are complementary.

TYPO3 core itself ships an AST-based check, typo3 fluid:analyze (TYPO3\CMS\Fluid\Command\AnalyzeCommand, since TYPO3 v13): it parses *.fluid.* files with the real Fluid TemplateParser and reports genuine parse errors plus dynamically-triggered deprecation warnings. It requires a fully bootable TYPO3 instance and only covers *.fluid.* files, so it doesn't replace this linter in standalone extension repos or lightweight CI jobs without a TYPO3 install. Where a full install is available, run both: fluid:analyze for real syntax/AST errors, this linter for encoding artifacts, namespace/XML issues, and deprecations simulated for TYPO3 versions that aren't even installed (--typo3-version=<major>).

Designed for AI-assisted development

This tool was created to catch a specific class of errors that LLMs introduce when generating Fluid templates. Language models produce correct Fluid logic reliably, but they occasionally introduce Unicode encoding artifacts that are nearly invisible in most editors: a " (U+201C, left double quotation mark) instead of a straight ", a (U+2033, double prime / inch mark) as an attribute delimiter, or «value» (angle quotation marks) as an attribute value. A human developer would never write class=«container» — but a language model can, especially when the surrounding context contains measurement data, quotation-heavy text, or content in languages that use these characters as standard punctuation.

The alternative — asking a second LLM to review every generated template for encoding errors — is slower, costs tokens, and is non-deterministic. A static linter is faster, deterministic, and integrates into CI with a single command.

Features

  • Invalid attribute quote detection — flags non-ASCII quote characters used as attribute value delimiters: typographic quotes (U+201C ", U+201D ", U+2018 ', U+2019 ', U+201A , U+201E ), backtick (U+0060 `), prime/inch marks (U+2032 , U+2033 ), angle quotes (U+00AB «, U+00BB », U+2039 , U+203A ); text content between tags (e.g. 1/2″ inside <f:case>) is not flagged
  • Missing namespace attribute — detects <html> tags with a Fluid xmlns declaration that are missing data-namespace-typo3-fluid="true", which causes duplicate <html> elements in the rendered output
  • Invalid namespace URIhttps://typo3.org/ns/ throws a runtime exception; the correct prefix is http://typo3.org/ns/
  • Fluid 5 compatibility — detects variable names starting with an underscore, which are forbidden in Fluid 5 (TYPO3 v14)
  • Deprecated syntax — flags empty parseFuncTSPath="", which causes a runtime error; use {field -> f:format.html()} instead
  • CDATA section detection — flags <![CDATA[...]]> inside <f:comment> blocks, the old pattern for safely commenting out Fluid code; deprecated in Fluid 4 and removed in Fluid 5; legitimate CDATA in XML/RSS templates and {{{expression}}} output syntax are not flagged
  • XML declaration warning — flags the <?xml ...?> processing instruction, which is unnecessary in Fluid templates and may trigger Quirks Mode
  • Debug ViewHelper detection — flags <f:debug> usage to prevent debug output from reaching production; configurable as warning (development) or error (live gate)
  • Deprecated ViewHelper detection — flags ViewHelpers and arguments that were deprecated or removed in a specific TYPO3 version: all <f:widget.*> (removed v11), getVars on <be:moduleLayout.button.shortcutButton> (deprecated v11), <f:be.container> (deprecated v11.3), <f:be.buttons.shortcut> and <f:base> (removed v12), <f:be.buttons.csh> and <f:be.labels.csh> (removed v13), <f:debug.render> and useNonce on <f:asset.*> (deprecated v14.2); requires --typo3-version=<major>
  • Config file support — per-project .fluid-lint.php with rule severity overrides; separate live config for CI release gates
  • Version-aware rules — pass --typo3-version=14 to activate rules specific to a TYPO3 major version
  • Three severity levelserror (exit code 1), warning, and info (both exit code 0)
  • GitHub Actions integration--format=github emits inline annotations with correct severity levels directly in pull request diffs
  • JSON output--format=json emits structured JSON for IDE plugins, pre-commit hooks, and custom tooling
  • Zero TYPO3 dependency — runs standalone in any PHP 8.2+ environment; no TYPO3 core required

Requirements

Requirement Version
PHP >= 8.2

Installation

composer require --dev oliverthiele/typo3-fluid-linter

Usage

Lint a directory recursively:

vendor/bin/fluid-lint packages/my_extension/Resources/Private/

Lint a single file:

vendor/bin/fluid-lint path/to/Template.html

Lint multiple paths:

vendor/bin/fluid-lint templates/ partials/ layouts/

Lint with TYPO3 version-specific rules:

vendor/bin/fluid-lint --typo3-version=14 packages/

Config file

Copy .fluid-lint.php to your project root. The linter auto-detects it on every run:

vendor/bin/fluid-lint packages/

For a stricter live/release gate (e.g. block on f:debug):

vendor/bin/fluid-lint --config=.fluid-lint.live.php packages/

Example .fluid-lint.php (development — f:debug is a warning):

<?php
use OliverThiele\FluidLinter\Config\LintConfig;

return (new LintConfig())
    ->rule('debug-viewhelper', severity: 'warning');

Example .fluid-lint.live.php (production — f:debug blocks the pipeline):

<?php
use OliverThiele\FluidLinter\Config\LintConfig;

return (new LintConfig())
    ->rule('debug-viewhelper', severity: 'error');

To disable a rule entirely, use disableRule():

<?php
use OliverThiele\FluidLinter\Config\LintConfig;

return (new LintConfig())
    ->disableRule('xml-declaration')       // never flag <?xml ?>
    ->disableRule('fluid-file-extension'); // ignore .html / .fluid.html coexistence

Automatic fixes

The --fix flag applies safe fixes automatically. Destructive operations (deleting files) additionally require --allow-risky:

# Rename .html → .fluid.html for files in partially migrated directories (safe)
vendor/bin/fluid-lint --fix --typo3-version=14 packages/

# Also delete orphaned .html files where .fluid.html already exists (destructive)
vendor/bin/fluid-lint --fix --allow-risky --typo3-version=14 packages/

Warning: --allow-risky permanently deletes files. Ensure your project is under version control before using it.

Currently fixable rules:

Rule Fix Risky?
xml-declaration Remove the <?xml ...?> processing instruction line No
parsefunc-tspath Remove the empty parseFuncTSPath="" attribute from the tag No
html-namespace-attribute Insert data-namespace-typo3-fluid="true" into the <html> opening tag No
https-namespace Replace https://typo3.org/ns/ with http://typo3.org/ns/ (all occurrences) No
fluid-file-extension (info) Rename Template.htmlTemplate.fluid.html No
fluid-file-extension (warning) Delete Template.html (.fluid.html counterpart kept) Yes

GitHub Actions

- name: Lint Fluid templates
  run: vendor/bin/fluid-lint --format=github --typo3-version=13 packages/

The --format=github flag emits ::error, ::warning, and ::notice annotations that GitHub Actions renders as inline comments in pull requests.

For a release gate that also blocks on f:debug:

- name: Lint Fluid templates (live gate)
  run: vendor/bin/fluid-lint --format=github --config=.fluid-lint.live.php packages/

CLI

Usage: fluid-lint [--format=github|json] [--fix [--allow-risky]] [--config=<file>] [--typo3-version=<major>] <path> [<path>...]

Options:
  --format=github          Emit GitHub Actions annotation format instead of console output
  --format=json            Emit structured JSON (violations array + summary object)
  --fix                    Apply safe automatic fixes (rename .html → .fluid.html)
  --allow-risky            Also apply destructive fixes (delete files) — requires --fix
  --config=<file>          Load rule configuration from a PHP file returning a LintConfig instance
  --typo3-version=<major>  Activate version-specific rules (e.g. --typo3-version=14)

Config file resolution (first match wins):
  1. --config=<file> (explicit path)
  2. .fluid-lint.php in the current working directory
  3. Built-in defaults (all rules enabled, default severities)

Exit codes:
  0    No errors (warnings and infos do not affect the exit code)
  1    One or more errors found, or path not found

Rules

Rule ID Severity Version
Invalid quote character as attribute delimiter (typographic, backtick, prime/inch, angle) typographic-quotes error all
Missing data-namespace-typo3-fluid="true" on <html> tag html-namespace-attribute error all
https:// in Fluid namespace URI throws an exception https-namespace error all
Fluid variable name starts with underscore (forbidden in Fluid 5) underscore-variable error 14+
Empty parseFuncTSPath="" causes a runtime error parsefunc-tspath error all
<![CDATA[ inside <f:comment> — deprecated in Fluid 4, removed in Fluid 5 cdata-section error all
<?xml ...?> processing instruction is unnecessary in Fluid templates xml-declaration warning all
f:debug ViewHelper found — remove before going live debug-viewhelper warning all
.html file has a .fluid.html counterpart — conflict, TYPO3 v14 may load the wrong file fluid-file-extension warning 14+
.html file has no .fluid.html counterpart — migration hint for v14 fluid-file-extension info 14+
Deprecated or removed ViewHelper/argument — f:widget.* (removed v11), getVars arg (deprecated v11), f:be.container (deprecated v11.3), f:be.buttons.shortcut + f:base (removed v12), f:be.buttons.csh + f:be.labels.csh (removed v13), f:debug.render + useNonce (deprecated v14.2) deprecated-viewhelper error / warning 11+

License

GPL-2.0-or-later — see LICENSE

Author

Oliver Thiele — oliver-thiele.de