Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

eslint-plugin-hbbtv-compat

ESLint plugin to validate JavaScript and CSS usage against the legacy HbbTV/OIPF compatibility profile.

The allow-lists are derived directly from the OIPF DAE Reference Guide, keeping only the elements that document marks as supported. You can override any of them to match a specific device profile.

Reference documents:

The allow-lists target Volume 5 V1.1. Where its Annex B contradicts the Reference Guide's tables, Annex B wins — see Known spec discrepancies.

V1.0 is stricter than V1.1: it does not add window.self, window.removeEventListener, window.setInterval, window.postMessage, KeyEvent.charCode or the Navigator property, and it profiles DOM Level 2 HTML as an explicit include-list rather than "all except three interfaces". To target a V1.0 device, narrow the lists with the supportedGlobals and supportedMembers options.

Requires ESLint 9 or 10 and flat config. Node ^20.19.0 || ^22.13.0 || >=24.

Usage

eslint.config.js:

const hbbtvCompat = require("eslint-plugin-hbbtv-compat");

module.exports = [
  hbbtvCompat.configs.recommended,
  hbbtvCompat.configs.html,
  hbbtvCompat.configs.css
];

configs.recommended enables all three rules for JavaScript. configs.html registers the HTML processor for **/*.html and **/*.htm and runs all three rules inside it: the processor extracts both <style> blocks (linted by the CSS rule) and inline <script> bodies (linted by the two JavaScript rules). configs.css registers the stylesheet processor for **/*.css and runs the CSS rule over whole stylesheets.

Do not combine with eslint-plugin-html

ESLint gives a file exactly one processor, and eslint-plugin-html does not declare itself through processor: — it patches Linter and bows out silently as soon as an explicit processor is set for the file. Setting processor: "hbbtv-compat/html" alongside it therefore raises no error; it just stops inline JavaScript from being linted by that plugin. Since the HTML processor here now covers <script> as well as <style>, drop eslint-plugin-html for HbbTV projects rather than running two configs and two eslint invocations.

A <script> element is skipped when it has a src attribute, is self-closing, has a whitespace-only body, or carries a type that is not JavaScript. Absent type, text/javascript, application/javascript, application/ecmascript and text/ecmascript count as JavaScript; type="module" does not, since an ES module cannot run on an edition 3 engine at all.

Inline <script> supports --fix: fix ranges from script blocks are translated back onto the document. Fixes reported inside a <style> block are dropped instead — that block is a synthesized string literal, so no offset in it maps onto the HTML.

Turn prettier/prettier off for *.html and *.css, the same way configs.css consumers already do. The rule sees an indented fragment stripped of its surrounding HTML and tries to reformat it back into a shape that does not fit the document.

{
  files: ["**/*.html", "**/*.htm", "**/*.css"],
  rules: { "prettier/prettier": "off" }
}

Not covered

Out of scope, by design:

  • on* handler attributes (onclick="…") — the attribute value is never extracted
  • inline style attributes — see no-unsupported-css
  • Vue, Svelte and other single-file component formats
  • elements found by the regex scan inside HTML comments, CDATA sections or attribute values, and start tags whose attribute value contains > — the document is scanned, not parsed

Core-rule presets

Two optional presets enable ESLint core rules that matter for this profile but that this plugin's own rules cannot see. They add no rules of their own — the checks are already implemented by well-tested core rules, and the presets exist only so you do not hand-maintain the same rules block in every project. Both are standalone and do not assume js/recommended is also extended.

module.exports = [
  hbbtvCompat.configs.recommended,
  hbbtvCompat.configs.legacyRuntime,
  hbbtvCompat.configs.styleOpinions, // optional
  hbbtvCompat.configs.html,
  hbbtvCompat.configs.css
];

configs.legacyRuntime — 10 core rules with a rationale in the specification or in the behaviour of the ECMA-262 edition 3 runtimes it profiles:

Rule Why
no-implied-eval Volume 5 V1.1 Annex B types setTimeout/setInterval as (Function f, Number d); the string form is off-spec
no-new-func a Function body passed as a string is never parsed as source, so it slips past no-post-es3-syntax and past an ecmaVersion: 3 parser alike
no-proto, no-iterator __proto__ and __iterator__ are legacy engine extensions, in neither edition 3 nor any DAE table
guard-for-in, no-extend-native section 3.1 exposes no Object.keys, so for...in is the only enumeration idiom and inherited properties are a live hazard
no-prototype-builtins section 3.1 lists Object.prototype.hasOwnProperty, but an edition 3 object can shadow it
block-scoped-var edition 3 has no let/const, making var hoisting the dominant scoping bug
radix edition 3 permits parseInt to read a leading zero as octal
no-with legal in edition 3, but defeats scope analysis for every other rule

no-proto and no-iterator are the clearest case for this preset: no-unsupported-js carries no type inference, so it never checks members on local variables and cannot catch them.

configs.styleOpinions — 19 general code-quality core rules (eqeqeq, curly, default-case, no-labels, yoda, no-param-reassign, …). These have no basis in either specification document and are bundled purely as a convenience. Skip this preset if you already have a house style; it is deliberately separate from legacyRuntime for that reason.

Neither preset includes no-alert (no-unsupported-js already reports alert, confirm, prompt and window.alert) or no-extra-label (no-labels already forbids every label).

To wire things up manually:

const hbbtvCompat = require("eslint-plugin-hbbtv-compat");

module.exports = [
  {
    plugins: { "hbbtv-compat": hbbtvCompat },
    rules: {
      "hbbtv-compat/no-unsupported-js": "error",
      "hbbtv-compat/no-unsupported-css": "error",
      "hbbtv-compat/no-post-es3-syntax": "error"
    }
  },
  {
    files: ["**/*.html"],
    plugins: { "hbbtv-compat": hbbtvCompat },
    processor: "hbbtv-compat/html",
    rules: {
      "hbbtv-compat/no-unsupported-css": "error",
      "hbbtv-compat/no-unsupported-js": "error",
      "hbbtv-compat/no-post-es3-syntax": "error"
    }
  },
  {
    files: ["**/*.css"],
    plugins: { "hbbtv-compat": hbbtvCompat },
    processor: "hbbtv-compat/css",
    rules: { "hbbtv-compat/no-unsupported-css": "error" }
  }
];

Processors and ecmaVersion

Both processors hand their CSS to the rule inside a generated JavaScript block. Inline <script> bodies are handed over as-is, without a wrapper. Every block — generated or raw — inherits its rules and its languageOptions from whichever config matched the source file. You cannot target them with a config block of your own, because a glob such as **/*.css/*.js does not match a block.

That inheritance is the constraint worth knowing about. In an HbbTV project the config block covering **/*.html and **/*.css normally sets ecmaVersion: 3, and the generated block is then parsed as edition 3 too. The generated code is therefore written as edition 3 — var plus a plain string literal — so it parses. Emitting a template literal instead costs you the CSS findings for that file and replaces them with a parse error on generated code you never wrote.

It applies to inline <script> the same way: with ecmaVersion: 3, post-edition-3 syntax in an inline script is a parse error rather than a no-post-es3-syntax report, and ESLint then drops every other finding for the whole document. If the inline scripts in your HTML are not edition 3 yet, leave the HTML config block on a newer ecmaVersion and let no-post-es3-syntax report the syntax instead.

The processor keeps blocks parseable where it can. HbbTV pages are XHTML, so inline scripts are commonly wrapped in //<![CDATA[ … //]]>, <![CDATA[ … ]]> or a legacy <!-- … //--> comment. Those markers are replaced with an equal number of spaces, so they cannot cause a parse error while every line, column and fix offset in the block still matches the document.

Rules

hbbtv-compat/no-unsupported-js

Reports globals and members outside the profile, based on sections 3.1–3.10 (ECMA-262 edition 3 basic objects, DOM0, DOM Level 2 Core/Events/Style/HTML/View, XMLHttpRequest) and 4.1 (OIPF JavaScript APIs).

Catches, for example, Promise, fetch, JSON, localStorage, new Image(), Object.keys, Array.from, [].indexOf, "x".trim(), fn.bind(), window.alert, window.removeEventListener (section 3.2 marks it unsupported on Window specifically), navigator.userAgent, document.querySelector and document.createElementNS.

Because the rule does no type inference, members are only checked on supported globals, on X.prototype, and on array/string literals. Members of local variables holding DOM elements or XMLHttpRequest instances are not checked.

Options

{
  "hbbtv-compat/no-unsupported-js": [
    "error",
    {
      "additionalSupportedGlobals": ["LibShim", "VendorBridge"],
      "supportedGlobals": ["window", "document", "Object", "Array"],
      "supportedMembers": {
        "Object": ["keys"],
        "Array.prototype": ["indexOf", "join"],
        "String.prototype": ["indexOf", "substring"]
      },
      "additionalSupportedMembers": {
        "window": ["stop"]
      }
    }
  ]
}

supportedGlobals and supportedMembers replace the built-in allow-lists. additionalSupportedGlobals extends the default global allow-list, which is the right knob for globals exported by external libraries. additionalSupportedMembers extends the member allow-list (the built-in one, or supportedMembers when set) per owner, which is the right knob for a single member the target devices are known to support despite the spec, such as window.stop.

hbbtv-compat/no-post-es3-syntax

Section 3.1 scopes JavaScript support to ECMA-262 edition 3. Newer syntax fails to parse on the device rather than failing at runtime, so it cannot be shimmed the way a missing API can.

Reports let/const, arrow functions, classes, template literals, for...of, spread/rest, destructuring, default parameters, generators, async/await, getters/setters, shorthand and computed properties, object method shorthand, object-literal and argument-list trailing commas, reserved words as unquoted property names or identifiers, import/export, optional chaining, ??, logical assignment, **, BigInt literals, numeric separators, catch without a binding, new.target, and regular expression flags beyond g, i and m.

Trailing commas in array literals are not reported: ECMA-262 edition 3 already allowed a trailing Elision there.

Options

{
  "hbbtv-compat/no-post-es3-syntax": [
    "error",
    { "allow": ["block-scoped-declaration", "template-literal"] }
  ]
}

allow opts individual features out by key, for code that is transpiled before shipping. Keys are the feature names used in the rule's messages, kebab-cased — see FEATURES in lib/rules/no-post-es3-syntax.js for the full list.

hbbtv-compat/no-unsupported-css

Covers sections 2.2–2.6 and 3.6.

  • Properties (2.2): the allow-list holds exactly the 90 properties Table 2 marks as supported.
  • At-rules (2.3): only @charset, @import and @media are allowed, so @font-face, @page and anything newer are reported.
  • Pseudo classes (2.4): :hover, :before, :after and any :: form are reported.
  • Selectors (2.5): attribute selectors, adjacent sibling (+) and general sibling (~).
  • Colors (2.6): hsl()/hsla(), currentColor, CSS2 system colors, non-HTML4 keywords in color properties, and hexadecimal colors that are not the six-digit form (#fff is out of profile; #ffffff is in).
  • DOM style properties (3.6): element.style.<name> is checked against the CSS2Properties subset, so style.cursor, style.tableLayout, style.borderSpacing, style.getPropertyCSSValue and modern properties such as style.transform are reported.

CSS text is scanned in:

  • assignments to *.style.cssText
  • setAttribute("style", "...")
  • tagged template literals named css, style or stylesheet
  • string/template variables with css or style in their identifier name
  • <style> blocks in HTML, via the hbbtv-compat/html processor
  • whole stylesheets, via the hbbtv-compat/css processor

The variable scan is a name heuristic. CSS built by concatenation, returned from a function, or passed straight into insertRule() is not seen.

Findings from a stylesheet are reported against the top-level rule they occur in, not the exact declaration line: the rule reports on the string that carried the CSS, so the hbbtv-compat/css processor emits one string per top-level rule to keep that granularity useful.

Inline style attributes are not scanned, and neither are on* handler attributes. The HTML processor reads <style> blocks and inline <script> bodies.

Templated stylesheets

The processors treat CSS as opaque text and the rule only inspects constructs it recognises, so template placeholders are neither parsed nor understood — they simply match nothing. A stylesheet containing color: {{ PRIMARY_COLOR }}; or {% if dark %} lints without special configuration and without false positives, which is what lets a stylesheet be linted before its template step runs.

This is a consequence of the design, not support for any template language. Brace-delimited syntaxes (Nunjucks, Jinja, Liquid, Handlebars, Mustache) work because their braces are balanced and so cannot close a CSS block early. Syntaxes that place a bare word where a value belongs — <%= color %> — can still be misread as a color keyword by the colour check; that is inherent to the rule's text scanning and applies equally to CSS embedded in JavaScript.

Options

{
  "hbbtv-compat/no-unsupported-css": [
    "error",
    {
      "allowedProperties": ["color", "background-color", "font-size"],
      "allowedStyleProperties": ["color", "backgroundColor"],
      "allowVendorPrefixed": true,
      "scanStyleVariables": true,
      "checkFeatures": true,
      "checkStyleProperties": true
    }
  ]
}

checkFeatures toggles the at-rule, pseudo class, selector and color checks. checkStyleProperties toggles the element.style.<name> check.

Known spec discrepancies

Resolved by Volume 5 V1.1 (Annex B overrides the Reference Guide)

Volume 5 Annex B, "Changes to Section 5.4", adds properties and methods to the Window scripting object that the Reference Guide's section 3.2 table marks as not supported. Annex B is normative, so these are allowed:

  • window.self — "Window self: reference to the current window" SHALL be added
  • window.removeEventListener — "allows DOM 2 event listener de-registration on the Window object"
  • KeyEvent.charCode — Annex B, "In Annex I": "attribute charCode must be added to the KeyEvent interface"
  • debug() as a bare global — section 7.15.5 says it "is available on the global (Window) object" and its own example calls it unqualified

Annex B also confirms exclusions the Reference Guide already had: image-orientation is OPTIONAL, font system values are not required, HTMLAppletElement/HTMLFrameElement/HTMLFrameSetElement are excluded, and Notation/Entity/EntityReference/ProcessingInstruction are excluded.

getPropertyCSSValue, CSSValue, RGBColor, Rect, Counter and CSSUnknownRule are declared OPTIONAL rather than forbidden. The plugin still reports them, since a compatibility linter cannot assume an optional feature is present.

Not resolved

Volume 5 delegates the CSS and DOM style profiles to CEA-2014-A Annexes H and I and does not restate their tables, so these remain open. Resolving them requires CEA-2014-A itself.

  • table-layout and border-spacing are marked supported in section 2.2 but their CSS2Properties counterparts tableLayout and borderSpacing are marked unsupported in section 3.6. The plugin follows each section for its own surface: the CSS property names are allowed in stylesheet text, the DOM property names are not.
  • :lang is marked supported in section 2.4 but the E:lang(c) selector form is marked unsupported in section 2.5. The plugin allows it, since that is the reading that avoids false positives.
  • Section 3.8 lists only Image and Option as constructor aliases, both unsupported. The DOM interface objects that carry constants (Node, DOMException, EventException, MutationEvent, CSSRule) are treated as available globals, restricted to those constants, because that is the only way the section 3.3/3.4/3.6 constants can be read.
  • Hexadecimal colors: section 2.1 permits only the six-digit form, so #fff is reported. Volume 5 is silent, so this stays as the Reference Guide states it.

Development

npm test

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages