Skip to content

Commit a29e3a8

Browse files
authored
feat: qwik eslint rules (#332)
1 parent 01a6329 commit a29e3a8

File tree

26 files changed

+1093
-33
lines changed

26 files changed

+1093
-33
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ jobs:
9999
path: dist-dev/create-qwik/
100100
if-no-files-found: error
101101

102+
- name: Build Eslint rules
103+
run: node scripts --eslint
104+
105+
- name: Print Eslint rules Dist Build
106+
run: tree dist-dev/eslint-plugin-qwik/
107+
108+
- name: Upload Eslint rules Build Artifacts
109+
uses: actions/upload-artifact@master
110+
with:
111+
name: dist-dev-eslint-plugin-qwik
112+
path: dist-dev/eslint-plugin-qwik/
113+
if-no-files-found: error
114+
102115
############ BUILD WASM ############
103116
build-wasm:
104117
name: Build WASM
@@ -371,6 +384,8 @@ jobs:
371384
mv builderio-qwik-distribution/* dist-dev/@builder.io-qwik/
372385
mkdir dist-dev/create-qwik/
373386
mv dist-dev-create-qwik/* dist-dev/create-qwik/
387+
mkdir dist-dev/eslint-plugin-qwik/
388+
mv dist-dev-eslint-plugin-qwik/* dist-dev/eslint-plugin-qwik/
374389
375390
- name: Cache NPM Dependencies
376391
uses: actions/cache@v2
@@ -417,9 +432,6 @@ jobs:
417432
- host: macos-latest
418433
browser: webkit
419434
node: 14.x
420-
- host: windows-latest
421-
browser: firefox
422-
node: 12.x
423435

424436
runs-on: ${{ matrix.settings.host }}
425437

@@ -447,6 +459,8 @@ jobs:
447459
mv builderio-qwik-distribution/* dist-dev/@builder.io-qwik/
448460
mkdir dist-dev/create-qwik/
449461
mv dist-dev-create-qwik/* dist-dev/create-qwik/
462+
mkdir dist-dev/eslint-plugin-qwik/
463+
mv dist-dev-eslint-plugin-qwik/* dist-dev/eslint-plugin-qwik/
450464
451465
- name: Cache NPM Dependencies
452466
if: ${{ needs.changes.outputs.fullbuild == 'true' }}
@@ -480,29 +494,24 @@ jobs:
480494

481495
steps:
482496
- name: Setup Node
483-
if: ${{ needs.changes.outputs.fullbuild == 'true' }}
484497
uses: actions/setup-node@v1
485498
with:
486499
node-version: 16.x
487500
registry-url: https://registry.npmjs.org/
488501

489502
- name: Checkout
490-
if: ${{ needs.changes.outputs.fullbuild == 'true' }}
491503
uses: actions/checkout@v2
492504

493505
- name: Cache NPM Dependencies
494-
if: ${{ needs.changes.outputs.fullbuild == 'true' }}
495506
uses: actions/cache@v2
496507
with:
497508
path: node_modules
498509
key: npm-cache-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
499510

500511
- name: Install NPM Dependencies
501-
if: ${{ needs.changes.outputs.fullbuild == 'true' }}
502512
run: yarn install --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
503513

504514
- name: Jest Unit Tests
505-
if: ${{ needs.changes.outputs.fullbuild == 'true' }}
506515
run: yarn test.unit
507516

508517
########### VALIDATE RUST ############

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
},
5959
"author": "Builder.io Team",
6060
"engines": {
61-
"node": ">=14.14.0"
61+
"node": ">=14"
6262
},
6363
"license": "MIT"
6464
}

eslint-rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# eslint-plugin-qwik

eslint-rules/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { noPropsDestructuring } from './lib/noPropsDestructuting';
2+
import { noUseAfterAwait } from './lib/noUseAfterAwait';
3+
4+
export const rules = {
5+
'no-props-destructuring': noPropsDestructuring,
6+
'no-use-after-await': noUseAfterAwait,
7+
};
8+
9+
export const configs = {
10+
recommended: {
11+
plugins: ['qwik'],
12+
rules: {
13+
'qwik/no-props-destructuring': 'error',
14+
'qwik/no-use-after-await': 'error',
15+
},
16+
},
17+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-disable no-console */
2+
/**
3+
* @license
4+
* Copyright Builder.io, Inc. All Rights Reserved.
5+
*
6+
* Use of this source code is governed by an MIT-style license that can be
7+
* found in the LICENSE file at https://github.com/BuilderIO/qwik/blob/main/LICENSE
8+
*/
9+
10+
import type { Rule } from 'eslint';
11+
12+
export const noPropsDestructuring: Rule.RuleModule = {
13+
meta: {
14+
type: 'problem',
15+
docs: {
16+
description: 'Object destructuring is not recomended for component$',
17+
category: 'Variables',
18+
recommended: true,
19+
url: 'https://github.com/BuilderIO/qwik',
20+
},
21+
},
22+
create(context) {
23+
return {
24+
"CallExpression[callee.name='component$'][arguments.0.params.0.type='ObjectPattern']"(
25+
node: any
26+
) {
27+
context.report({
28+
node: node.arguments[0].params[0],
29+
message: 'Props destructuring is not a good practice in Qwik',
30+
});
31+
},
32+
};
33+
},
34+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-disable no-console */
2+
/**
3+
* @license
4+
* Copyright Builder.io, Inc. All Rights Reserved.
5+
*
6+
* Use of this source code is governed by an MIT-style license that can be
7+
* found in the LICENSE file at https://github.com/BuilderIO/qwik/blob/main/LICENSE
8+
*/
9+
10+
import type { Rule } from 'eslint';
11+
12+
export const noUseAfterAwait: Rule.RuleModule = {
13+
meta: {
14+
type: 'problem',
15+
docs: {
16+
description: 'Object destructuring is not recomended for component$',
17+
category: 'Variables',
18+
recommended: true,
19+
url: 'https://github.com/BuilderIO/qwik',
20+
},
21+
},
22+
create(context) {
23+
const stack: { await: boolean }[] = [];
24+
return {
25+
ArrowFunctionExpression() {
26+
stack.push({ await: false });
27+
},
28+
'ArrowFunctionExpression:exit'() {
29+
stack.pop();
30+
},
31+
AwaitExpression() {
32+
const last = stack[stack.length - 1];
33+
if (last) {
34+
last.await = true;
35+
}
36+
},
37+
'CallExpression[callee.name=/^use/]'(node: any) {
38+
const last = stack[stack.length - 1];
39+
if (last && last.await) {
40+
context.report({
41+
node,
42+
message: 'Calling use* methods after await is not safe.',
43+
});
44+
}
45+
},
46+
};
47+
},
48+
};

eslint-rules/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "eslint-plugin-qwik",
3+
"version": "0.0.18-6",
4+
"description": "An Open-Source sub-framework designed with a focus on server-side-rendering, lazy-loading, and styling/animation.",
5+
"main": "index.js",
6+
"author": "",
7+
"license": "MIT",
8+
"peerDependencies": {
9+
"eslint": ">= 8"
10+
},
11+
"homepage": "https://github.com/BuilderIO/qwik#readme",
12+
"repository": {
13+
"type": "git",
14+
"url": "https://github.com/BuilderIO/qwik.git"
15+
},
16+
"bugs": {
17+
"url": "https://github.com/BuilderIO/qwik/issues"
18+
},
19+
"keywords": [
20+
"builder.io",
21+
"qwik",
22+
"eslint"
23+
],
24+
"engines": {
25+
"node": ">=14"
26+
}
27+
}

eslint-rules/qwik.unit.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/* eslint-disable */
2+
// @ts-ignore
3+
const RuleTester = require('eslint').RuleTester;
4+
import { rules } from './index';
5+
6+
const testConfig = {
7+
env: {
8+
es6: true,
9+
},
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
ecmaVersion: 2021,
15+
sourceType: 'module',
16+
},
17+
};
18+
describe('no-props-destructuring', () => {
19+
const ruleTester = new RuleTester(testConfig);
20+
ruleTester.run('my-rule', rules['no-props-destructuring'], {
21+
valid: [
22+
`export const HelloWorld = component$((props) => {
23+
return $(({prop}) => {
24+
return (
25+
<Host>
26+
{prop}
27+
{props}
28+
</Host>
29+
);
30+
});
31+
});`,
32+
],
33+
invalid: [
34+
{
35+
code: `export const HelloWorld = component$(({prop}) => {
36+
return $(() => {
37+
return (
38+
<Host>
39+
{prop}
40+
</Host>
41+
);
42+
});
43+
});`,
44+
errors: ['Props destructuring is not a good practice in Qwik'],
45+
},
46+
],
47+
});
48+
});
49+
50+
describe('no-use-after-await', () => {
51+
const ruleTester = new RuleTester(testConfig);
52+
ruleTester.run('my-rule', rules['no-use-after-await'], {
53+
valid: [
54+
`export const HelloWorld = component$(async () => {
55+
useMethod();
56+
await something();
57+
return $(() => {
58+
return <Host></Host>
59+
});
60+
});`,
61+
`export const HelloWorld = component$(async () => {
62+
useMethod();
63+
await something();
64+
await stuff();
65+
return $(() => {
66+
useHostElement();
67+
return <Host></Host>
68+
});
69+
});`,
70+
],
71+
invalid: [
72+
{
73+
code: `export const HelloWorld = component$(async () => {
74+
await something();
75+
useMethod();
76+
return $(() => {
77+
return (
78+
<Host>
79+
{prop}
80+
</Host>
81+
);
82+
});
83+
});`,
84+
errors: ['Calling use* methods after await is not safe.'],
85+
},
86+
{
87+
code: `export const HelloWorld = component$(async () => {
88+
if (stuff) {
89+
await something();
90+
}
91+
useMethod();
92+
return $(() => {
93+
return (
94+
<Host>
95+
{prop}
96+
</Host>
97+
);
98+
});
99+
});`,
100+
errors: ['Calling use* methods after await is not safe.'],
101+
},
102+
],
103+
});
104+
});
105+
106+
export {};

eslint-rules/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"rootDir": ".",
4+
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
5+
},
6+
"include": ["."]
7+
}

0 commit comments

Comments
 (0)