xslint

A linter for XSL/XSLT stylesheets. It first checks that every stylesheet is well-formed and every XPath expression compiles, then flags stylistic, semantic, and logical mistakes — each pinpointed to its exact line and column, with a fix where the correction is unambiguous. Think of it as the checks ESLint gives JavaScript, but for XSL.

Run it

npx @maxonfjvipon/xslint path/to/stylesheets

It also runs in your editor — VS Code and compatible editors and JetBrains IDEs — in CI via the GitHub Action, or embedded through the language server.

Manual

What it reports

[ERROR]   sheet.xsl(2:1) The xsl:output instruction is missing. (not-using-output)
[WARNING] sheet.xsl(3:3) The match attribute starts with //, which scans the whole tree. (starts-with-double-slash)
[WARNING] sheet.xsl(4:5) A variable has a single-character name. (short-names)

Proven on real code

Pointed at core stylesheets from the three most widely-used XSLT projects — DocBook-XSL (1.0), TEI (2.0), and DITA-OT (1.0/2.0), 70 files in all — xslint surfaced 1,974 findings across 22 different checks, with no false positives from its validators: 106 xsl:choose blocks with no xsl:otherwise, 67 unused named templates, 40 stylesheet functions never called, and more. Real stylistic and logical findings in code that has shipped for decades.

Checks

71 checks, each with its rationale. A run reports the 33 marked recommended unless asked for --preset all: what a processor refuses, and the dead code whose report is almost never wrong.

Check Type Severity Description
blank-nested-if xpath warning An xsl:if holds nothing but another xsl:if, stating one condition in two places. Combine the tests with 'and' in one xsl:if, unless the outer test guards the inner one.
duplicate-param-name xpath error recommended Two xsl:param siblings share a @name. Each parameter of a template, function, or stylesheet must have a distinct name.
duplicate-with-param-name xpath error recommended A single call passes two xsl:with-param with the same @name. Each parameter can be supplied only once.
empty-choose xpath error recommended An xsl:choose has no xsl:when branch. Add one, or drop the xsl:choose and keep its content directly.
empty-content-in-instructions xpath warning recommended An instruction element such as xsl:for-each or xsl:if has no content. Add content or remove the empty element.
empty-variable xpath warning An empty xsl:variable binds the empty string without saying so. State the value with select="''" or give it some content.
function-complexity xpath warning The function contains more than 50 XSLT elements. Split it into smaller functions.
function-template-is-not-child-of-stylesheet xpath error recommended An xsl:function or xsl:template is nested inside another element. Move it up to the top level, as a child of the stylesheet element.
function-use-in-xslt-1 xpath error recommended An xsl:function requires XSLT 2.0 or later, and the version in force here is below that. Use a named template, or raise that version to 2.0.
incorrect-use-of-boolean-constants xpath warning suggested fix recommended The test is the string 'true' or 'false', a non-empty string that is always true, not a boolean. Use true() or false() instead.
malformed-version-in-stylesheet xpath error recommended A declared version is not a decimal number, so it names no XSLT version and every check gated on one stays quiet. Declare it as a decimal, such as 2.0.
missing-id-in-stylesheet xpath warning The stylesheet element has no @id attribute. Declare it to specify the unique identifier explicitly.
missing-or-empty-href xpath error recommended The @href is missing or empty, so this pulls in no module and no processor will load the stylesheet. Name the module to pull in.
missing-or-empty-name xpath error recommended The @name is missing or empty, so it holds no QName and no processor will load this stylesheet. Give it a name.
missing-version-in-stylesheet xpath warning suggested fix The version attribute is missing on the stylesheet's root element. Declare it to specify the stylesheet version explicitly.
mode-or-priority-without-match xpath error suggested fix recommended An xsl:template that has no match attribute must have no mode attribute and no priority attribute. Add match attribute or remove mode or priority.
modern-construct-in-xslt-1 xpath error recommended This XSLT 2.0 construct stands where the version in force is below 2.0, which a conformant 1.0 processor rejects. Use a 1.0 equivalent, or raise that version to 2.0.
name-starts-with-numeric xpath error recommended A variable, parameter, function, or template name starts with a digit, so no processor loads this stylesheet. Rename it to start with a letter.
not-creating-attribute-correctly xpath warning xsl:attribute with a static name on a literal element can be a literal attribute (an AVT for a computed value). Write it inline.
not-using-attribute-test-in-if-when-node xpath error recommended An xsl:if or xsl:when has no @test attribute. Add one, or remove the element.
otherwise-not-last xpath error recommended An xsl:otherwise must be the last child of its xsl:choose. Move it after every xsl:when.
oversized-template xpath warning The template contains more than 100 XSLT elements. Decompose it with apply-templates into focused templates.
param-after-content xpath error recommended An xsl:param must be declared before any other content in its xsl:template or xsl:function. Move it above the content.
setting-value-of-variable-incorrectly xpath warning A variable or parameter is assigned via a nested xsl:value-of instead of the select attribute. Use select syntax instead.
short-names xpath warning A variable, parameter, function, or template has a single-character name. Use a descriptive name that reveals intent.
sort-not-first xpath error recommended An xsl:sort must come before any other content in its xsl:for-each or xsl:apply-templates. Move it to the front.
stylesheet-has-no-templates xpath warning The stylesheet declares nothing at all, so it contributes nothing to a transformation. Add a declaration to it.
template-has-no-name-or-match xpath error recommended An xsl:template must have a @match or @name attribute, or both. Add one of them.
text-outside-xsl-text xpath warning suggested fix Literal text appears directly inside an XSLT instruction element. Wrap it in xsl:text.
too-many-templates xpath warning The stylesheet declares 10 or more templates, too many to read in one file. Split it into imported modules.
use-choose-without-otherwise xpath warning xsl:choose has no xsl:otherwise branch. Add xsl:otherwise to handle unmatched cases explicitly.
use-single-option-for-choose xpath warning xsl:choose contains only one xsl:when branch. Replace it with xsl:if.
using-disable-output-escaping xpath warning suggested fix disable-output-escaping='yes' is set, which produces implementation-defined output. Use xsl:copy-of or literal elements instead.
using-not-outermost-stylesheet xpath error recommended An xsl:stylesheet, xsl:transform or xsl:package is nested inside another element. It must be the outermost element of the document.
variable-or-param-with-select-and-content xpath error suggested fix recommended An xsl:variable, xsl:param or xsl:with-param has both a select attribute and a non-empty body. Use only one way to set the value.
when-or-otherwise-outside-choose xpath error recommended An xsl:when or xsl:otherwise is valid only as a child of xsl:choose. Move it into an xsl:choose, or use xsl:if.
with-param-use-in-invalid-parent-node xpath error recommended xsl:with-param stands under an element taking no parameters at the version in force, which a processor of that version refuses. Move it into a call, or raise the version.
with-param-without-name xpath error recommended xsl:with-param requires a @name attribute to bind the parameter it passes. Add one.
unreachable-function corpus warning recommended A stylesheet function is called only from functions that are themselves never reached, so it can never run. Remove it, or call it from a template.
unused-function corpus warning recommended A stylesheet function is never called in any expression across the corpus. Remove it or call it.
unused-named-template corpus warning A named template is never invoked via xsl:call-template from anything that runs. Remove it or call it.
unused-variable corpus warning A variable is declared but never referenced by $name across the corpus. Remove it or use it.
invalid-xpath-expression validation error recommended An XPath expression is malformed and cannot be parsed. Fix its syntax.
malformed-stylesheet validation error recommended The stylesheet is not well-formed XML and cannot be parsed. Fix its syntax.
syntax-newer-than-xslt-version validation error recommended This XPath syntax belongs to a later XSLT version than the one in force, so a conformant processor rejects it. Raise the version, or respell the construct.
broken-href format error recommended The @href names no file on disk, so no processor will load this stylesheet. Point it at the module, or restore the file.
circular-import format error recommended The stylesheet pulls in itself, or a module that pulls it back, through xsl:import or xsl:include, which is a static error. Break the cycle.
confusing-variable-and-node format warning suggested fix A variable name is used bare as a node selector. Use $name syntax to reference the variable.
count-compared-to-zero format warning safe fix count(...) is compared with 0 to test existence, which walks the whole sequence. Test existence directly instead (exists()/empty() on XSLT 2.0+, boolean()/not() on 1.0).
leaking-result-namespace format warning suggested fix The xsl:stylesheet declares a namespace prefix used only in stylesheet logic, which a literal result element copies into the output. Add the prefix to exclude-result-prefixes.
name-compared-to-string format warning suggested fix name() or local-name() is compared with a string, which is prefix-fragile and slower than a node test. Match the element with a node test on the self axis instead.
not-creating-element-correctly format warning xsl:element is used with a static name. Use a literal result element instead.
not-using-output format warning The xsl:output instruction is missing. Declare it to specify the serialization format explicitly.
output-method-xml format warning suggested fix xsl:output declares method='xml' while the root template builds an HTML document. Change the method to 'html'.
predicate-position-literal format warning safe fix A positional predicate is written the long way; [position() = N] is just [N] and [position() = last()] is just [last()]. Shorten it.
redundant-boolean-call format warning safe fix A boolean() call stands where nothing but a truth is taken, so the wrapper is redundant. Drop it.
redundant-double-negation format warning safe fix A double negation not(not(x)) is redundant; it is just boolean(x), and where nothing but a truth is taken simply x. Replace it.
redundant-import format warning suggested fix recommended The stylesheet imports or includes the same module more than once. Keep a single xsl:import/xsl:include of it and remove the other.
redundant-namespace-declarations format warning suggested fix The xsl:stylesheet declares a namespace prefix that nothing in it uses. Remove the declaration, or exclude the prefix where a literal result element copies it out.
redundant-whitespace format warning safe fix An XPath expression has redundant whitespace. Remove the extra spaces.
scans-whole-document format warning An expression opens a path with //, which scans the whole document each time it runs. Name the path, use xsl:key, or bind the scan once in a top-level variable.
starts-with-double-slash format warning safe fix suggested fix A pattern alternative opens with //, which at most demands a document-node root. Drop it, and set an explicit priority if the template must rank as before.
string-length-compared-to-zero format warning suggested fix string-length(...) is compared with 0 to test emptiness, which measures the whole string. Compare the value with '' instead.
template-writes-nothing format warning recommended This template declares variables and writes nothing to the result tree, so applying it produces nothing. Write the output it was meant to build, or empty it.
translate-for-case format warning suggested fix translate() folds case over the ASCII alphabet alone. Use lower-case() or upper-case(), which are shorter and Unicode-aware.
unabbreviated-axis format warning safe fix Verbose axis specifiers child::, attribute::, parent::node(), or self::node() are used. Replace them with the node name alone, @, .., or . respectively.
undefined-variable format error recommended A variable is referenced where no binding in scope declares it. Declare it, or correct the name to one in scope.
unused-function-template-parameter format warning A parameter is declared but never referenced in the function or template body. Remove it or use it.
use-double-slash format warning A pattern contains //, which matches at any depth and is broader than intended. Use a specific path.
use-node-set-extension format warning safe fix The node-set() extension function is unnecessary in XSLT 2.0 and later. Query the variable directly without it.
using-namespace-axis format warning The namespace:: axis is deprecated in XSLT 2.0 and later. Use in-scope-prefixes() and namespace-uri-for-prefix() instead.