eslint-plugin-html-attributes
v1.0.6
Published
ESLint plugin to enforce required attributes on HTML elements in JSX/React.
Maintainers
Readme
eslint-plugin-html-attributess
ESLint plugin to enforce required attributes on HTML elements in JSX/React with configurable auto-fix strategies.
🎮 Want to USE this, not change it? → DEMO
🔧 Want to CHANGE this because you use it? → .dev/
Features
- ✅ Configurable for any HTML tag (button, input, div, etc.)
- ✅ Configurable attribute name (data-testid, data-qa, etc.)
- ✅ Multiple auto-fix strategies:
- uuid: Deterministic UUID generation based on element content and location
- llm: LLM-powered semantic ID generation via HTTP API
- custom: Language-agnostic custom generator scripts (Python, Bash, Node.js, etc.)
- ✅ Pattern validation with regex
- ✅ Supports JSX and template literals
Installation
npm install --save-dev eslint-plugin-html-attributesUsage
ESLint Flat Config (recommended)
import htmlAttributePlugin from 'eslint-plugin-html-attributes';
export default [
{
plugins: {
'html-attributes': htmlAttributePlugin,
},
rules: {
'html-attribute/require-attribute': ['error', {
htmlTag: 'button',
attributeName: 'data-testid',
fix: 'uuid'
}],
},
},
];Legacy .eslintrc
{
"plugins": ["html-attributes"],
"rules": {
"html-attribute/html-attribute": ["error", {
"htmlTag": "button",
"attributeName": "data-testid",
"fix": "uuid"
}]
}
}Configuration Options
htmlTag (string)
The HTML tag to enforce the attribute on.
Default: "button"
Examples: "button", "input", "div", "a"
attributeName (string)
The attribute name to require.
Default: "data-testid"
Examples: "data-testid", "data-qa", "data-cy"
pattern (string, optional)
A regex pattern that the attribute value must match. Useful for enforcing naming conventions.
Default: undefined (no pattern validation)
Examples:
"^[a-z0-9]+(-[a-z0-9]+)*$"- Kebab-case (lowercase letters, numbers, and dashes)"^input-[a-z0-9-]+$"- Must start with "input-""^[A-Z][a-zA-Z0-9]*$"- PascalCase"^[a-z_]+$"- Snake_case (lowercase letters and underscores)
Usage:
{
"html-attribute/html-attribute": ["error", {
"htmlTag": "input",
"attributeName": "data-testid",
"pattern": "^input-[a-z0-9-]+$" // All inputs must start with "input-"
}]
}Note: UUID auto-fix generates standard UUIDs which may not match custom patterns. For pattern-compliant IDs, use custom fix strategy.
fix (enum: 'uuid' | 'llm' | 'custom')
The ID generation strategy for auto-fix.
Default: "uuid"
uuid - Deterministic UUID Generation
Generates a consistent UUID based on the element's content and location in the file.
{
"html-attribute/require-attribute": ["error", {
"htmlTag": "button",
"fix": "uuid"
}]
}llm - LLM-Generated Semantic IDs
Uses an HTTP service (such as an LLM API) to generate semantic IDs based on element context.
Configuration:
{
"html-attribute/require-attribute": ["error", {
"htmlTag": "button",
"fix": "llm",
"llmConfig": {
"url": "https://your-llm-api.com/generate",
"apiKey": "your-api-key",
"method": "POST",
"requestBody": {
"prompt": "Generate a test ID for: {{elementText}}"
},
"responseKey": "id"
}
}]
}Environment Variables (via .env file):
Create a .env file in your project root:
# .env
UUID_GENERATOR_API_URL=https://api.openai.com/v1/chat/completions
UUID_GENERATOR_API_URL_API_KEY=sk-proj-your-api-key-hereThen run ESLint with dotenv:
# Install dotenv
npm install --save-dev dotenv
# Run ESLint with environment variables loaded
node -r dotenv/config node_modules/.bin/eslint --fix
# Or add to your package.json scripts:
# "lint:fix": "node -r dotenv/config eslint --fix"Environment Variables:
UUID_GENERATOR_API_URL- LLM API endpoint URLUUID_GENERATOR_API_URL_API_KEY- API key for authentication
Note: See .env.example for a template.
custom - Custom Generator Script
Executes a language-agnostic script that generates custom IDs. The script receives the element text and location as command-line arguments and should print a single line to stdout.
Example Python script (my-generator.py):
#!/usr/bin/env python3
import sys
import hashlib
element_text = sys.argv[1]
location = sys.argv[2]
# Generate custom ID based on element text
id_value = hashlib.md5(element_text.encode()).hexdigest()[:8]
print(f"custom-{id_value}")Example Bash script (my-generator.sh):
#!/bin/bash
ELEMENT_TEXT="$1"
LOCATION="$2"
# Generate custom ID
echo "btn-$(echo -n "$ELEMENT_TEXT" | md5sum | cut -c1-8)"Example Node.js script (my-generator.js):
#!/usr/bin/env node
const [,, elementText, location] = process.argv;
const crypto = require('crypto');
// Generate custom ID
const hash = crypto.createHash('md5').update(elementText).digest('hex').slice(0, 8);
console.log(`custom-${hash}`);Configuration:
{
"html-attribute/require-attribute": ["error", {
"htmlTag": "button",
"fix": "custom",
"customGeneratorPath": "./scripts/my-generator.py"
}]
}Important:
- Make sure your script is executable (
chmod +x script-name) - Custom scripts can access environment variables from
.envwhen ESLint is run withnode -r dotenv/config - See
DEMO/scripts/llm-generator.shfor an example of using LLM APIs in custom scripts
Error Handling & Robustness:
Custom generator scripts should implement graceful error handling. When an API call fails (non-200 status), the script should:
- Log the error to stderr for visibility
- Generate a fallback ID to ensure the fix never fails
- Return a valid ID to stdout
Example error handling in bash:
# Make API request
RESPONSE=$(curl -s -w "\n%{http_code}" "$API_URL" ...)
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
# Check status
if [ "$HTTP_CODE" != "200" ]; then
echo "Error: API returned status $HTTP_CODE" >&2
# Generate fallback ID
echo "fallback-$(echo -n "$ELEMENT_TEXT" | md5sum | cut -c1-8)"
exit 0
fi
# Extract and return API response
echo "$GENERATED_ID"Common HTTP error codes to handle:
- 403 - Invalid API key or insufficient permissions
- 429 - Rate limit exceeded
- 500 - Server error
- 503 - Service unavailable
The plugin ensures ESLint auto-fix always succeeds by falling back to UUID generation if the custom script fails.
customGeneratorPath (string)
Path to an executable script (required when fix is "custom"). The script will be called with two arguments:
- elementText - The full JSX/HTML text of the element
- location - The file path and line number (format:
path/to/file.tsx:123)
The script should print a single line to stdout containing the generated ID.
llmConfig (object)
Configuration for LLM-based ID generation (used when fix is "llm").
Properties:
url(string) - LLM API endpoint URLapiKey(string, optional) - API key for authenticationheaders(object, optional) - Additional HTTP headersmethod(string, optional) - HTTP method:GET,POST, orPUT(default:POST)requestBody(object, optional) - Request body template with placeholders:{{elementText}}- Replaced with element JSX/HTML{{location}}- Replaced with file location
responseKey(string, optional) - JSON path to extract ID from response (default:"id")
Multiple HTML Tags
You can check multiple HTML tags with different settings in the same file by passing an array of configurations:
Simple Array Configuration (Recommended)
import htmlAttributePlugin from 'eslint-plugin-html-attributes';
export default [
{
files: ['src/**/*.tsx'],
plugins: { 'html-attributes': htmlAttributePlugin },
rules: {
'html-attribute/require-attribute': ['error', [
// Check all buttons with kebab-case pattern
{
htmlTag: 'button',
attributeName: 'data-testid',
pattern: '^[a-z0-9]+(-[a-z0-9]+)*$',
fix: 'uuid'
},
// Check all inputs with "input-" prefix
{
htmlTag: 'input',
attributeName: 'data-testid',
pattern: '^input-[a-z0-9-]+$',
fix: 'uuid'
},
// Check all selects with "select-" prefix
{
htmlTag: 'select',
attributeName: 'data-testid',
pattern: '^select-[a-z0-9-]+$',
fix: 'uuid'
},
]],
},
},
];Advanced: Different Fix Strategies per Tag
You can use different auto-fix strategies for different tags:
export default [
{
files: ['src/**/*.tsx'],
plugins: { 'html-attributes': htmlAttributePlugin },
rules: {
'html-attribute/require-attribute': ['error', [
// Buttons use custom semantic ID generator
{
htmlTag: 'button',
fix: 'custom',
customGeneratorPath: './scripts/semantic-id-generator.py'
},
// Inputs use standard UUID
{
htmlTag: 'input',
fix: 'uuid'
},
// Links use custom attribute name
{
htmlTag: 'a',
attributeName: 'data-qa',
fix: 'uuid'
},
]],
},
},
];Alternative: Separate Config Blocks
If you need different file patterns for different tags, you can still use separate config blocks:
export default [
// Check buttons in all component files
{
files: ['src/components/**/*.tsx'],
plugins: { 'html-attributes': htmlAttributePlugin },
rules: {
'html-attribute/require-attribute': ['error', {
htmlTag: 'button',
attributeName: 'data-testid',
pattern: '^[a-z0-9]+(-[a-z0-9]+)*$',
fix: 'uuid'
}],
},
},
// Check inputs only in form files
{
files: ['src/forms/**/*.tsx'],
plugins: { 'html-attributes': htmlAttributePlugin },
rules: {
'html-attribute/require-attribute': ['error', {
htmlTag: 'input',
attributeName: 'data-testid',
pattern: '^input-[a-z0-9-]+$',
fix: 'uuid'
}],
},
},
];Rules
html-attribute/require-attribute
Enforces that specified HTML elements have the required attribute.
❌ Incorrect
<button onClick={handleClick}>Click me</button>
<button type="submit">Submit</button>✅ Correct
<button data-testid="a1b2c3d4-..." onClick={handleClick}>Click me</button>
<button data-testid="e5f6g7h8-..." type="submit">Submit</button>Development
To develop the plugin:
cd eslint-plugin-html-attributes
yarn install
yarn buildTo try the plugin:
cd DEMO
yarn install
yarn lintLicense
MIT
