@brickflow/lint 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/brick.js ADDED
@@ -0,0 +1,5 @@
1
+ import constCase from './rules/const-case.js'
2
+
3
+ const plugin = { rules: { 'const-case': constCase } }
4
+
5
+ export default plugin
package/index.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./index.js').default
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createLintConfig } from './shared.js'
2
+
3
+ export default createLintConfig()
package/nuxt.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./nuxt.js').default
package/nuxt.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createLintConfig } from './shared.js'
2
+
3
+ export default createLintConfig({ includeVue: true })
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@brickflow/lint",
3
+ "version": "0.0.4",
4
+ "type": "module",
5
+ "description": "Shared ESLint configs for brickflow workspaces.",
6
+ "files": [
7
+ "index.cjs",
8
+ "index.js",
9
+ "nuxt.cjs",
10
+ "nuxt.js",
11
+ "shared.js",
12
+ "brick.js",
13
+ "rules"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "import": "./index.js",
18
+ "require": "./index.cjs",
19
+ "default": "./index.js"
20
+ },
21
+ "./nuxt": {
22
+ "import": "./nuxt.js",
23
+ "require": "./nuxt.cjs",
24
+ "default": "./nuxt.js"
25
+ }
26
+ },
27
+ "scripts": {
28
+ "lint": "pnpm run --if-present typecheck && eslint -v && NODE_ENV=deploy eslint --cache .",
29
+ "lint:fix": "pnpm exec eslint . --fix",
30
+ "format": "pnpm exec prettier . --ignore-path ../../.prettierignore --write",
31
+ "format:check": "pnpm exec prettier . --ignore-path ../../.prettierignore --check"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@eslint/js": "^9.39.0",
38
+ "eslint-config-prettier": "10.1.8",
39
+ "eslint-plugin-perfectionist": "4.15.0",
40
+ "eslint-plugin-prettier": "5.5.4",
41
+ "eslint-plugin-promise": "7.2.1",
42
+ "eslint-plugin-regexp": "2.10.0",
43
+ "eslint-plugin-vue": "10.5.0",
44
+ "globals": "^16.4.0",
45
+ "typescript-eslint": "8.44.1",
46
+ "vue-eslint-parser": "10.2.0"
47
+ },
48
+ "peerDependencies": {
49
+ "eslint": ">=9",
50
+ "prettier": ">=3"
51
+ },
52
+ "author": "",
53
+ "license": "ISC"
54
+ }
@@ -0,0 +1,58 @@
1
+ const isUpperCase = (name) => name && name === name.toUpperCase()
2
+
3
+ const isSpecialChars = (name) => name && /^[$_]$/.test(name)
4
+
5
+ const isCalleeRequire = (init) => {
6
+ return init && init.callee && init.callee.name === 'require'
7
+ }
8
+
9
+ const isInitTypeLiteral = (init) => init && init.type === 'Literal'
10
+
11
+ const isInitTypeNegativeLiteral = (init) =>
12
+ init && init.type === 'UnaryExpression' && init.operator === '-' && init.argument.type === 'Literal'
13
+
14
+ const isLiteral = (init) =>
15
+ isInitTypeLiteral(init) || isInitTypeNegativeLiteral(init) || isInitTypeBinaryExpression(init)
16
+
17
+ function isInitTypeBinaryExpression(init) {
18
+ return (
19
+ init &&
20
+ init.type === 'BinaryExpression' &&
21
+ ['*', '+', '-', '/'].includes(init.operator) &&
22
+ isLiteral(init.left) &&
23
+ isLiteral(init.right)
24
+ )
25
+ }
26
+
27
+ const messages = {
28
+ lower: 'const/let should be in lower case',
29
+ upper: 'const should be in upper case',
30
+ }
31
+
32
+ const rule = {
33
+ create: ({ report }) => ({
34
+ VariableDeclaration: (node) => {
35
+ if (node.kind === 'const') {
36
+ node.declarations.forEach(({ id: { name }, init }) => {
37
+ if (!isUpperCase(name) && isLiteral(init)) {
38
+ report({ message: messages.upper, node })
39
+ }
40
+
41
+ if (isUpperCase(name) && !isLiteral(init) && !isCalleeRequire(init) && !isSpecialChars(name)) {
42
+ report({ message: messages.lower, node })
43
+ }
44
+ })
45
+ }
46
+
47
+ if (node.kind === 'let') {
48
+ node.declarations.forEach(({ id: { name }, init }) => {
49
+ if (isUpperCase(name) && !isCalleeRequire(init) && !isSpecialChars(name)) {
50
+ report({ message: messages.lower, node })
51
+ }
52
+ })
53
+ }
54
+ },
55
+ }),
56
+ }
57
+
58
+ export default rule
package/shared.js ADDED
@@ -0,0 +1,333 @@
1
+ import js from '@eslint/js'
2
+ import configPrettier from 'eslint-config-prettier'
3
+ import perfectionist from 'eslint-plugin-perfectionist'
4
+ import prettierRecommended from 'eslint-plugin-prettier/recommended'
5
+ import pluginPromise from 'eslint-plugin-promise'
6
+ import regexp from 'eslint-plugin-regexp'
7
+ import pluginVue from 'eslint-plugin-vue'
8
+ import globals from 'globals'
9
+ import tseslint from 'typescript-eslint'
10
+ import vueParser from 'vue-eslint-parser'
11
+
12
+ import brick from './brick.js'
13
+
14
+ const warn = process.env.NODE_ENV === 'deploy' ? 'error' : 'warn'
15
+
16
+ const ignores = [
17
+ '**/node_modules/**',
18
+ '**/dist/**',
19
+ '**/.nuxt/**',
20
+ '**/.output/**',
21
+ '**/coverage/**',
22
+ '**/icon.d.ts',
23
+ '**/img.d.ts',
24
+ '.prettierrc.cjs',
25
+ 'eslint.config.cjs',
26
+ ]
27
+
28
+ const baseRules = {
29
+ '@typescript-eslint/consistent-type-definitions': 'off',
30
+ '@typescript-eslint/default-param-last': 'error',
31
+ '@typescript-eslint/explicit-function-return-type': [
32
+ 'error',
33
+ {
34
+ allowExpressions: true,
35
+ },
36
+ ],
37
+ '@typescript-eslint/explicit-module-boundary-types': 'error',
38
+ '@typescript-eslint/no-dynamic-delete': 'off',
39
+ '@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'always' }],
40
+ '@typescript-eslint/no-invalid-void-type': 'off',
41
+ '@typescript-eslint/no-require-imports': 'off',
42
+ '@typescript-eslint/no-restricted-types': [
43
+ 'error',
44
+ {
45
+ types: {
46
+ object: {
47
+ message: 'Use Record<string, unknown> instead of object for better type safety.',
48
+ },
49
+ },
50
+ },
51
+ ],
52
+ '@typescript-eslint/no-shadow': 'error',
53
+ '@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
54
+ '@typescript-eslint/no-unused-vars': [
55
+ warn,
56
+ {
57
+ argsIgnorePattern: '^_',
58
+ ignoreRestSiblings: true,
59
+ varsIgnorePattern: '^_',
60
+ },
61
+ ],
62
+ '@typescript-eslint/no-use-before-define': [
63
+ warn,
64
+ {
65
+ functions: false,
66
+ },
67
+ ],
68
+ 'array-callback-return': 'error',
69
+ 'block-scoped-var': 'error',
70
+ 'brick/const-case': 'off',
71
+ curly: 'error',
72
+ 'default-case': 'error',
73
+ 'default-case-last': 'error',
74
+ 'default-param-last': 'off',
75
+ 'dot-notation': 'error',
76
+ eqeqeq: 'error',
77
+ 'logical-assignment-operators': ['error', 'always'],
78
+ 'new-cap': ['error', { newIsCap: true, properties: false }],
79
+ 'no-alert': warn,
80
+ 'no-await-in-loop': 'off',
81
+ 'no-constant-binary-expression': 'error',
82
+ 'no-constructor-return': 'error',
83
+ 'no-debugger': warn,
84
+ 'no-duplicate-imports': 'error',
85
+ 'no-else-return': 'error',
86
+ 'no-empty-function': 'error',
87
+ 'no-empty-static-block': 'error',
88
+ 'no-eq-null': 'error',
89
+ 'no-eval': 'error',
90
+ 'no-extra-semi': 'off',
91
+ 'no-implicit-coercion': 'error',
92
+ 'no-implied-eval': 'error',
93
+ 'no-invalid-this': 'off',
94
+ 'no-lone-blocks': 'error',
95
+ 'no-lonely-if': 'error',
96
+ 'no-loop-func': 'error',
97
+ 'no-multi-assign': 'error',
98
+ 'no-negated-condition': 'error',
99
+ 'no-nested-ternary': 'error',
100
+ 'no-new-func': 'error',
101
+ 'no-new-native-nonconstructor': 'error',
102
+ 'no-new-wrappers': 'error',
103
+ 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
104
+ 'no-proto': 'error',
105
+ 'no-restricted-syntax': [
106
+ 'error',
107
+ {
108
+ message: 'Write function props only with a prefix ^on | ^set.',
109
+ selector:
110
+ 'CallExpression[callee.name=defineProps] TSPropertySignature[typeAnnotation.typeAnnotation.type=TSFunctionType][key.name!=/^on|^set/]',
111
+ },
112
+ {
113
+ message: 'Use ^set only with function which must return value.',
114
+ selector:
115
+ 'CallExpression[callee.name=defineProps] TSPropertySignature[typeAnnotation.typeAnnotation.type=TSFunctionType][typeAnnotation.typeAnnotation.returnType.typeAnnotation.type=TSVoidKeyword][key.name=/^set/]',
116
+ },
117
+ {
118
+ message: 'Use ^on only with function who work as Event|Emit.',
119
+ selector:
120
+ 'CallExpression[callee.name=defineProps] TSPropertySignature[typeAnnotation.typeAnnotation.type=TSFunctionType][typeAnnotation.typeAnnotation.returnType.typeAnnotation.type!=TSVoidKeyword][typeAnnotation.typeAnnotation.returnType.typeAnnotation.type!=TSTypeReference][key.name=/^on/]',
121
+ },
122
+ {
123
+ message: 'Use ^on only with function who work as Event|Emit.',
124
+ selector:
125
+ 'CallExpression[callee.name=defineProps] TSPropertySignature[typeAnnotation.typeAnnotation.type=TSFunctionType][typeAnnotation.typeAnnotation.returnType.typeAnnotation.type=TSTypeReference][key.name=/^on/] TSTypeParameterInstantiation > :not(TSVoidKeyword)',
126
+ },
127
+ {
128
+ message: 'Use ^set only with function which must return value.',
129
+ selector:
130
+ 'CallExpression[callee.name=defineProps] TSPropertySignature[typeAnnotation.typeAnnotation.type=TSFunctionType][typeAnnotation.typeAnnotation.returnType.typeAnnotation.type=TSTypeReference][key.name=/^set/] TSTypeParameterInstantiation > TSVoidKeyword',
131
+ },
132
+ {
133
+ message: 'defineEmits is forbidden. Use props or alternative pattern.',
134
+ selector: "CallExpression[callee.name='defineEmits']",
135
+ },
136
+ ],
137
+ 'no-return-assign': 'error',
138
+ 'no-script-url': 'error',
139
+ 'no-self-compare': 'error',
140
+ 'no-undef': 'off',
141
+ 'no-unmodified-loop-condition': 'error',
142
+ 'no-unneeded-ternary': 'error',
143
+ 'no-unreachable-loop': 'error',
144
+ 'no-unused-private-class-members': 'error',
145
+ 'no-unused-vars': 'off',
146
+ 'no-useless-call': 'error',
147
+ 'no-useless-computed-key': 'error',
148
+ 'no-useless-concat': 'error',
149
+ 'no-useless-rename': 'error',
150
+ 'no-var': 'error',
151
+ 'no-void': 'error',
152
+ 'object-shorthand': warn,
153
+ 'one-var': ['error', 'never'],
154
+ 'operator-assignment': ['error'],
155
+ 'perfectionist/sort-vue-attributes': 'off',
156
+ 'prefer-arrow-callback': 'error',
157
+ 'prefer-const': 'error',
158
+ 'prefer-exponentiation-operator': 'error',
159
+ 'prefer-numeric-literals': 'error',
160
+ 'prefer-object-has-own': 'error',
161
+ 'prefer-object-spread': 'error',
162
+ 'prefer-regex-literals': 'error',
163
+ 'prefer-rest-params': 'error',
164
+ 'prefer-spread': 'error',
165
+ 'prefer-template': 'error',
166
+ 'prettier/prettier': warn,
167
+ 'promise/always-return': 'error',
168
+ 'promise/no-nesting': 'error',
169
+ 'promise/no-new-statics': 'error',
170
+ 'promise/no-return-in-finally': 'error',
171
+ 'promise/no-return-wrap': 'error',
172
+ 'promise/param-names': 'error',
173
+ 'promise/prefer-await-to-then': warn,
174
+ 'promise/valid-params': 'error',
175
+ 'require-await': warn,
176
+ yoda: 'error',
177
+ }
178
+
179
+ const vueRules = {
180
+ 'vue/attribute-hyphenation': [
181
+ 'error',
182
+ 'never',
183
+ {
184
+ ignore: [],
185
+ },
186
+ ],
187
+ 'vue/block-lang': [
188
+ 'error',
189
+ {
190
+ script: {
191
+ lang: 'ts',
192
+ },
193
+ },
194
+ ],
195
+ 'vue/component-name-in-template-casing': [
196
+ 'error',
197
+ 'PascalCase',
198
+ {
199
+ registeredComponentsOnly: false,
200
+ },
201
+ ],
202
+ 'vue/custom-event-name-casing': ['error', 'camelCase'],
203
+ 'vue/define-macros-order': [
204
+ 'error',
205
+ {
206
+ order: ['defineOptions', 'defineProps', 'defineSlots', 'defineEmits'],
207
+ },
208
+ ],
209
+ 'vue/define-props-declaration': ['error', 'type-based'],
210
+ 'vue/html-button-has-type': [
211
+ 'error',
212
+ {
213
+ button: true,
214
+ reset: true,
215
+ submit: true,
216
+ },
217
+ ],
218
+ 'vue/match-component-import-name': ['error'],
219
+ 'vue/multi-word-component-names': 'off',
220
+ 'vue/next-tick-style': ['error', 'promise'],
221
+ 'vue/no-boolean-default': ['error', 'default-false'],
222
+ 'vue/no-constant-condition': ['error'],
223
+ 'vue/no-duplicate-attr-inheritance': ['error'],
224
+ 'vue/no-empty-component-block': ['error'],
225
+ 'vue/no-restricted-props': [
226
+ 'error',
227
+ {
228
+ message: 'Don\'t use word "need" in props',
229
+ name: '/^need/',
230
+ },
231
+ ],
232
+ 'vue/no-restricted-syntax': [
233
+ 'error',
234
+ {
235
+ message: 'Use "@" symbol for Event|Emit.',
236
+ selector: 'VIdentifier[rawName=/^on/]',
237
+ },
238
+ ],
239
+ 'vue/no-template-target-blank': [
240
+ 'error',
241
+ {
242
+ allowReferrer: false,
243
+ enforceDynamicLinks: 'always',
244
+ },
245
+ ],
246
+ 'vue/no-unused-components': warn,
247
+ 'vue/no-unused-refs': ['error'],
248
+ 'vue/no-unused-vars': warn,
249
+ 'vue/no-useless-v-bind': [
250
+ 'error',
251
+ {
252
+ ignoreIncludesComment: false,
253
+ ignoreStringEscape: false,
254
+ },
255
+ ],
256
+ 'vue/no-v-html': 'off',
257
+ 'vue/no-v-text': ['error'],
258
+ 'vue/padding-line-between-blocks': ['error', 'always'],
259
+ 'vue/prefer-true-attribute-shorthand': ['error', 'always'],
260
+ 'vue/require-component-is': 'off',
261
+ 'vue/require-expose': ['error'],
262
+ 'vue/v-for-delimiter-style': ['error'],
263
+ 'vue/v-on-event-hyphenation': [
264
+ 'error',
265
+ 'never',
266
+ {
267
+ ignore: [],
268
+ },
269
+ ],
270
+ }
271
+
272
+ const jsRules = {
273
+ '@typescript-eslint/explicit-function-return-type': 'off',
274
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
275
+ '@typescript-eslint/no-var-requires': 'off',
276
+ }
277
+
278
+ export function createLintConfig({ includeVue = false } = {}) {
279
+ const languageGlobals = includeVue ? { ...globals.browser, ...globals.node } : { ...globals.node }
280
+
281
+ return [
282
+ js.configs.recommended,
283
+ ...tseslint.configs.strict,
284
+ ...(includeVue ? pluginVue.configs['flat/recommended'] : []),
285
+ perfectionist.configs['recommended-natural'],
286
+ regexp.configs['flat/recommended'],
287
+ pluginPromise.configs['flat/recommended'],
288
+ prettierRecommended,
289
+ configPrettier,
290
+ {
291
+ plugins: {
292
+ brick,
293
+ },
294
+ },
295
+ {
296
+ files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],
297
+ languageOptions: {
298
+ ecmaVersion: 'latest',
299
+ globals: languageGlobals,
300
+ parser: tseslint.parser,
301
+ sourceType: 'module',
302
+ },
303
+ rules: baseRules,
304
+ },
305
+ ...(includeVue
306
+ ? [
307
+ {
308
+ files: ['**/*.vue'],
309
+ languageOptions: {
310
+ ecmaVersion: 'latest',
311
+ globals: languageGlobals,
312
+ parser: vueParser,
313
+ parserOptions: {
314
+ parser: tseslint.parser,
315
+ },
316
+ sourceType: 'module',
317
+ },
318
+ rules: {
319
+ ...baseRules,
320
+ ...vueRules,
321
+ },
322
+ },
323
+ ]
324
+ : []),
325
+ {
326
+ files: ['**/*.{js,mjs,cjs}'],
327
+ rules: jsRules,
328
+ },
329
+ {
330
+ ignores,
331
+ },
332
+ ]
333
+ }