An Interest In:
Web News this Week
- April 2, 2024
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
Build a custom Javascript linter in 5 minutes
Creating a custom linter can be a great way to enforce coding standards and detect code smells. In this tutorial, we'll use Sylver, a source code query engine to build a custom Javascript linter in just a few lines of code.
Sylver's main interface is a REPL console, in which we can load the source code of our project to query it using a SQL-like query language called SYLQ
. Once we'll have authored SYLQ
queries expressing our linting rules, we'll be able to save them into a ruleset that can be run like a traditional linter.
Installation
If sylver --version
doesn't output a version number >= 0.1.9
, go to https://sylver.dev to download a fresh copy of the software.
Starting the REPL
Starting the REPL is as simple as invoking the following command at the root of your project:
sylver query --files="src/**/*.js" --spec=https://github.com/sylver-dev/javascript.git#javascript.yaml
The REPL can be exited by pressing Ctrl+C
or typing :quit
at the prompt.
We can now execute SYLQ
queries by typing the code of the query, followed by a ;
. For instance: to retrieve all the method definitions (denoted by the node type MethodDefinition):
match MethodDefinition;
The results of the query will be formatted as follow:
[...]$0 [MethodDefinition src/store/createArticles.js:36:5-38:5]$1 [MethodDefinition src/store/createArticles.js:39:5-41:5]$2 [MethodDefinition src/store/createArticles.js:42:5-59:5]$3 [MethodDefinition src/store/createArticles.js:60:5-77:5]$4 [MethodDefinition src/store/createArticles.js:78:5-83:5]$5 [MethodDefinition src/store/createArticles.js:84:5-89:5][...]
The code of a given method definition can be displayed by typing :print
followed by the node alias (for instance: :print $3
). The parse tree can be displayed using the :print_ast
command (for instance: :print_ast $3
).
Rule1: use of the ==
operator
For our first rule, we'd like to detect uses of the unsafe ==
operator for checking equality. The first step is to get familiar with the tree structure of Javascript's binary expressions, so let's print a BinaryExpression
node along with its AST:
> match BinaryExpression;[...]$43 [BinaryExpression src/pages/Article/Comments.js:7:31-7:77][...]> :print $43currentUser.username == comment.author.username> :print_ast $43BinaryExpression {. left: MemberExpression {. . object: Identifier { currentUser }. . property: Identifier { username }. }. operator: EqEq { == }. right: MemberExpression {. . object: MemberExpression {. . . object: Identifier { comment }. . . property: Identifier { author }. . }. . property: Identifier { username }. }}
It appears that the nodes violating our rule are the BinaryExpression
nodes for which the operator
field contains an EqEq
node. This can be easily expressed in SYLQ
:
match BinaryExpression(operator: EqEq);
Rule2: functions with too many parameters
For our second linting rule, we'd like to identify functions that have more than 6 parameters.
Here is the relevant part of the parse tree of a Function
node:
Function {. async: AsyncModifier { async }. name: Identifier { send }. parameters: FormalParameters {. . params: List {. . . FormalParameter {. . . . value: Identifier { method }. . . }. . . FormalParameter {. . . . value: Identifier { url }. . . }. . . FormalParameter {. . . . value: Identifier { data }. . . }. . . FormalParameter {. . . . value: Identifier { resKey }. . . }. . }. }. body: StatementBlock {[...]
Function parameters are represented by FormalParameters
nodes with a params
field containing the actual function parameters. In our query, the condition regarding the length of the params
list can be specified in a when
clause, as follows:
match f@FormalParameters when f.params.length > 6;
Rule3: JSX 'img' elements without an 'alt' attribute
For our last rule, we'd like to identify <img>
elements that miss the alt
attribute. img
elements are self-closing, so we'll start by looking at the parse tree of a JsxSelfClosingElement
node:
> match JsxSelfClosingElement;[...]$73 [JsxSelfClosingElement src/pages/Article/Comments.js:21:11-21:55][...]> :print $73<img src={image} class="comment-author-img"/>> :print_ast $73JsxSelfClosingElement {. name: Identifier { img }. attribute: List {. . JsxAttribute {. . . name: Identifier { src }. . . value: JsxExpression {. . . . Identifier { image }. . . }. . }. . JsxAttribute {. . . name: Identifier { class }. . . value: String { "comment-author-img" }. . }. }}
In order to find the img
elements that have no JsxAttribute with named alt
in their attribute
list, we can use a list quantifying expression, as illustrated in the following query:
match j@JsxSelfClosingElement(name: "img") when no j.attribute match JsxAttribute(name: "alt");
Creating the ruleset
The following ruleset uses our linting rules:
id: customLinterlanguage: "https://github.com/sylver-dev/javascript.git#javascript.yaml"rules: - id: unsafeEq message: equality comparison with `==` operator severity: warning query: "match BinaryExpression(operator: EqEq)" - id: tooManyParams message: function has too many parameters severity: info note: According to our style guide, functions should have less than 6 parameters. query: match f@FormalParameters when f.params.length > 6 - id: missingAlt message: <img> tags should have an "alt" attribute severity: error query: "match j@JsxSelfClosingElement(name: 'img') when no j.attribute match JsxAttribute(name: 'alt')"
Assuming that it is stored in a file called custom_linter.yaml
at the root of our project, we can run it with the following command:
sylver ruleset run --files="src/**/*.js" --rulesets=custom_linter.yaml
Getting updates
For more informations about new features and/or cool SYLQ
one-liners, connect with Sylver on Twitter or Discord!
Original Link: https://dev.to/geoffreycopin/build-a-custom-javascript-linter-in-5-minutes-4a1j
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To