Frontend Development 24 min read

Developing ESLint and StyleLint Plugins for San‑Native Code Linting

The article explains how to design and implement ESLint and StyleLint plugins for the San‑Native framework, detailing AST generation, processor creation, rule definitions for JavaScript and CSS, configuration sharing, and how these tools provide real‑time linting feedback for developers.

Baidu App Technology
Baidu App Technology
Baidu App Technology
Developing ESLint and StyleLint Plugins for San‑Native Code Linting

Code style and quality checks are a long‑standing topic in software development. In the front‑end world, linting usually focuses on indentation, trailing commas, semicolons, etc., but it also needs to handle special scenarios specific to a framework. This article describes the design and implementation of a linting solution for the san-native framework, which includes two plugins: @baidu/eslint-plugin-san-native and @baidu/stylelint-plugin-san-native .

1. Introduction to AST

The core of static analysis tools such as ESLint is the Abstract Syntax Tree (AST). An AST is a tree‑structured representation of source code that abstracts away concrete syntax. Parsers like @babel/parser , espree , or acorn generate JavaScript ASTs, while postcss and cssTree generate CSS ASTs. The AST generation consists of lexical analysis (tokenization) followed by syntactic analysis (building node relationships).

2. ESLint rule basics

An ESLint rule exports an object with meta (type, docs, schema, messages) and a create function that returns a visitor map. The visitor receives AST nodes of a specific type and can call context.report to emit errors.

module.exports = {
    meta: {
        type: "problem",
        docs: {...},
        schema: [],
        messages: {readonlyMember: "The members of '{{name}}' are read-only."}
    },
    create(context) {
        return {
            ImportDeclaration(node) {
                context.report({node, messageId: "readonlyMember", data: {name: 'xxx'}});
            }
        };
    }
};

3. ESLint configuration reuse

Projects can share configurations via the extends field. For example, @ecomfe/eslint-config provides a base configuration, and @ecomfe/eslint-config/san adds San‑specific rules. The final configuration merges parser , plugins , and rules from all extended files, with later entries overriding earlier ones.

module.exports = {
    extends: ['@ecomfe/eslint-config', '@ecomfe/eslint-config/san'],
    parser: 'san-eslint-parser',
    plugins: ['@baidu/san-native'],
    rules: {
        'no-style-float': 'error',
        // ...other rules
    }
};

4. Processor implementation

ESLint processes a file in three stages: preprocess → parser → postprocess . The san-native processor extracts the <style> block, parses it with postcss , and caches the resulting CSS AST for later rule execution.

const {styleAstCache} = require('./utils/cache');
module.exports = {
    preprocess(code, filename) {
        styleAstCache.storeAst(styleHelper.getStyleContentInfo(code));
        return [code];
    },
    postprocess(messages) {
        styleAstCache.storeAst(null);
        return messages[0];
    }
};

The helper that parses the <style> block chooses the appropriate syntax (less, sass, scss) and returns the AST together with line/offset information.

module.exports = {
    getAst(syntax, content, plugins) {
        let ast = null;
        try { ast = syntax ? processor.process(content, {syntax}).sync() : processor.process(content).sync(); }
        catch (e) {}
        return ast;
    },
    getStyleContentInfo(text) {
        const lines = text.split('\n');
        const content = /()([\s\S]*?)<\/style>/gi.exec(text);
        const langMatch = /\slang\s*=\s*("[^"]*"|'[^']*')/.exec(content[1]);
        const lang = langMatch[1].replace(/["'\s]/g, '');
        const astFn = lang ? this.getAst : this.getAst.bind(null, syntaxs[lang]);
        return {startLine: lines.indexOf(content[1]), ast: astFn(content[3]), startOffset: text.indexOf(content[3])};
    }
};

5. Rule examples for San‑Native

Rules can inspect the template AST via context.parserServices.defineTemplateBodyVisitor . For instance, a rule that checks unsupported events or inline styles in a .san component:

module.exports = {
    meta: {...},
    create(context) {
        return context.parserServices.defineTemplateBodyVisitor(context, {
            'VElement'(node) { /* check element name, attributes, nesting */ },
            VAttribute(node) { /* parse inline style values */ }
        });
    }
};

Another rule validates that a built‑in component such as lottie-view contains either a src or source attribute.

6. StyleLint plugin for San‑Native

StyleLint works similarly to ESLint but operates on CSS ASTs. A StyleLint rule receives the root CSS node and can walk declarations with root.walkDecls . Example rule that forbids unsupported justify-content values:

const stylelint = require('stylelint');
const ruleName = 'stylelint-plugin-san-native/valid-justify-content';
const messages = stylelint.utils.ruleMessages(ruleName, {expected: () => "Only some values of 'justify-content' are supported in san-native"});
module.exports = stylelint.createPlugin(ruleName, (primary) => {
    return (root, result) => {
        if (!primary) return;
        root.walkDecls(decl => {
            const parsed = valueParser(decl.value);
            parsed.walk(node => {
                if (['flex-start','flex-end','center','space-between','space-around'].indexOf(node.value) < 0) {
                    stylelint.utils.report({
                        index: declarationValueIndex(decl) + node.sourceIndex,
                        message: messages.expected(),
                        node: decl,
                        ruleName,
                        result
                    });
                }
            });
        });
    };
});
module.exports.ruleName = ruleName;
module.exports.messages = messages;

The plugin bundles multiple such rules in index.js and provides configuration files ( always.js , temporary.js ) that can be extended in a project's .stylelintrc.js .

7. Summary

The article demonstrates how to build ESLint and StyleLint plugins for the San‑Native framework, covering AST generation, processor design, rule creation, and configuration reuse. By exposing the underlying AST nodes and providing precise location information, the plugins enable real‑time feedback in editors such as VSCode, helping developers catch unsupported styles, events, and component nesting rules early in the development cycle.

JavaScriptASTplugin developmentESLintlintingSan-Nativestylelint
Baidu App Technology
Written by

Baidu App Technology

Official Baidu App Tech Account

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.