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.

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

69 checks, each with its rationale:

Check Type Severity Description
blank-nested-if xpath warning A nested xsl:if inside another xsl:if is prohibited. Combine conditions with 'and' into a single xsl:if.
duplicate-param-name xpath error 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 A single call passes two xsl:with-param with the same @name. Each parameter can be supplied only once.
empty-choose xpath error '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 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 for no reason. Give it a @select or some content, or remove it.
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 'xsl:function' or 'xsl:template' are used in the wrong places. Use 'xsl:function' and 'xsl:template' as child nodes of 'xsl:stylesheet'.
function-use-in-xslt-1 xpath error '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 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 A declared version is not a number, so it names no XSLT version and no version gate under it clears its floor, leaving every check about a construct a later version introduced quiet rather than guessing. Declare it as a decimal, such as 2.0
missing-id-in-stylesheet xpath warning The '@id' attribute is missing in the 'xsl:stylesheet' element. Declare it to specify the unique identifier explicitly.
missing-or-empty-href xpath error The '@href' is missing or empty here, so this pulls in no other module and no processor will load the stylesheet
missing-or-empty-name xpath error The '@name' is missing or empty here, and neither is a QName, so no processor will load this stylesheet
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 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 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 warning A variable, function, or template name starts with a digit, which is invalid in XPath. 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 'xsl:if' or 'xsl:when' don't have attribute @test. Add this attribute or delete node.
otherwise-not-last xpath error '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 '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 is assigned via a nested xsl:value-of instead of the select attribute. Use select syntax instead.
short-names xpath warning A variable, function, or template has a single-character name. Use a descriptive name that reveals intent.
sort-not-first xpath error '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, or delete the file.
template-has-no-name-or-match xpath error 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 'xsl:' 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 xsl:stylesheet 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 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 'xsl:when' and 'xsl:otherwise' are valid only as children of 'xsl:choose'. Move it into an 'xsl:choose', or use 'xsl:if'.
with-param-use-in-invalid-parent-node xpath error xsl:with-param is allowed only within xsl:call-template, xsl:apply-templates, xsl:next-match, xsl:next-iteration, or an xsl:apply-imports whose version in force is 2.0 or later. Move it into a call, or raise that version.
with-param-without-name xpath error xsl:with-param requires a @name attribute to bind the parameter it passes. Add one.
unreachable-function corpus warning A stylesheet function is called only from within a recursion cycle that nothing else enters, so it can never run. Remove it, or call it from a template.
unused-function corpus warning 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. 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 An XPath expression is malformed and cannot be parsed. Fix its syntax.
malformed-stylesheet validation error The stylesheet is not well-formed XML and cannot be parsed. Fix its syntax.
syntax-newer-than-xslt-version validation error This XPath syntax belongs to a later XSLT version than the one in force here, so a conformant processor of the version in force rejects it. Raise that version, or spell the construct the way it allows.
circular-import format error The stylesheet is part of an xsl:import/xsl:include cycle — it pulls in, directly or through a chain, a stylesheet that pulls it back (or imports itself). This is a static error. Break the cycle so no module depends on one that depends on it.
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. Use self::name 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 — a test or a use-when, an operand of and/or, the argument of not(), the condition of an if, the body of a satisfies — 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 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 safe fix The xsl:stylesheet declares a namespace prefix that is never used. Remove the redundant xmlns declaration.
redundant-whitespace format warning safe fix An XPath expression has redundant whitespace. Remove the extra spaces.
select-starts-with-double-slash format warning suggested fix The select attribute starts with //, which scans the whole document from the root. Anchor it with .// or a specific path.
starts-with-double-slash format warning safe fix suggested fix A pattern alternative starts with //, which is redundant since every XSLT pattern already matches at any depth, and it lowers the rule's default priority from 0.5 to that of the step alone. Remove the leading // and give the rule an explicit priority if it must keep ranking as it does.
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 This template declares variables and writes nothing to the result tree, so applying it produces nothing
translate-for-case format warning suggested fix translate() folds case over the ASCII alphabet; XSLT 2.0 does this with 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.
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.