@ithinkdt/lint 4.0.0-5 → 4.0.0-9

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/src/index.js CHANGED
@@ -1,118 +1,255 @@
1
- import { config as tsconfig } from 'typescript-eslint'
1
+ import { env } from 'node:process'
2
2
 
3
3
  import unocss from '@unocss/eslint-config/flat'
4
+ import { mergeProcessors } from 'eslint-merge-processors'
5
+ import tsdoc from 'eslint-plugin-tsdoc'
6
+ import processorVueBlocks from 'eslint-processor-vue-blocks'
4
7
 
5
- import base, { disableJsTypeChecked, tsparser, vueparser, vue, vitest, playwright } from './base.js'
8
+ import { base, defaultEsFiles, defaultJsFiles, defaultTsFiles, disableTypeChecked, playwright, tsParser, vitest, vue, vueParser } from './base.js'
9
+ import ithinkdt from './rules/index.js'
6
10
 
7
- export default function config({ unocss: withUnoCSS } = {}) {
8
- return tsconfig(
9
- ...base,
10
- ...vue.configs['flat/recommended'],
11
- {
12
- settings: {
13
- 'import-x/resolver': {
14
- 'custom-alias': {
15
- alias: { '@': './src' },
16
- extensions: ['.js', '.jsx', '.ts', 'tsx'],
17
- },
18
- typescript: {
11
+ export * from './base.js'
12
+
13
+ export default function config({ unocss: withUnoCSS, stylistic: withStylistic, tsconfigRootDir } = {}) {
14
+ const extraFileExtensions = ['.vue']
15
+
16
+ withStylistic = withStylistic === true
17
+ ? {
18
+ indent: 4,
19
+ quotes: 'single',
20
+ semi: false,
21
+ jsx: true,
22
+ braceStyle: '1tbs',
23
+ }
24
+ : withStylistic
25
+
26
+ return base({ inCI: !!env.CI && env.CI !== 'false', stylistic: withStylistic })
27
+ .append(
28
+ ithinkdt.configs.recommended,
29
+ vue.configs['flat/recommended'].map((it) => {
30
+ return {
31
+ ...it,
32
+ files: defaultEsFiles,
33
+ }
34
+ }),
35
+ {
36
+ files: defaultEsFiles,
37
+ languageOptions: {
38
+ parser: tsParser,
39
+ parserOptions: {
19
40
  project: true,
41
+ tsconfigRootDir,
42
+ sourceType: 'module',
43
+ ecmaVersion: 'latest',
20
44
  },
21
45
  },
46
+ rules: {
47
+ 'vue/block-lang': ['warn', {
48
+ script: {
49
+ lang: ['ts', 'tsx'],
50
+ },
51
+ }],
52
+ 'vue/block-order': ['error', {
53
+ order: ['script', 'template', 'style'],
54
+ }],
55
+ 'vue/component-name-in-template-casing': ['error',
56
+ 'PascalCase',
57
+ ],
58
+ 'vue/component-options-name-casing': ['error',
59
+ 'PascalCase',
60
+ ],
61
+ // this is deprecated
62
+ 'vue/component-tags-order': 'off',
63
+ 'vue/custom-event-name-casing': ['error',
64
+ 'camelCase',
65
+ ],
66
+ 'vue/define-macros-order': ['warn', {
67
+ order: ['definePage', 'defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots'],
68
+ }],
69
+ 'vue/dot-location': ['error',
70
+ 'property',
71
+ ],
72
+ 'vue/dot-notation': ['error', {
73
+ allowKeywords: true,
74
+ }],
75
+ 'vue/eqeqeq': ['error',
76
+ 'smart',
77
+ ],
78
+ 'vue/no-mutating-props': ['error', {
79
+ shallowOnly: true,
80
+ }],
81
+ 'vue/html-indent': ['error',
82
+ withStylistic?.indent,
83
+ ],
84
+ 'vue/html-quotes': ['error',
85
+ 'double',
86
+ ],
87
+ 'vue/max-attributes-per-line': 'off',
88
+ 'vue/multi-word-component-names': 'off',
89
+ 'vue/no-dupe-keys': 'off',
90
+ 'vue/no-empty-pattern': 'error',
91
+ 'vue/no-irregular-whitespace': 'error',
92
+ 'vue/no-loss-of-precision': 'error',
93
+ 'vue/no-restricted-syntax': ['error',
94
+ 'DebuggerStatement',
95
+ 'LabeledStatement',
96
+ 'WithStatement',
97
+ ],
98
+ 'vue/no-restricted-v-bind': ['error',
99
+ '/^v-/',
100
+ ],
101
+ 'vue/no-setup-props-reactivity-loss': 'off',
102
+ 'vue/no-sparse-arrays': 'error',
103
+ 'vue/no-unused-refs': 'error',
104
+ 'vue/no-useless-v-bind': 'error',
105
+ 'vue/no-v-html': 'off',
106
+ 'vue/object-shorthand': ['error',
107
+ 'always',
108
+ {
109
+ avoidQuotes: true,
110
+ ignoreConstructors: false,
111
+ },
112
+ ],
113
+ 'vue/prefer-separate-static-class': 'error',
114
+ 'vue/prefer-template': 'error',
115
+ 'vue/prop-name-casing': ['error',
116
+ 'camelCase',
117
+ ],
118
+ 'vue/require-default-prop': 'off',
119
+ 'vue/require-prop-types': 'off',
120
+ 'vue/space-infix-ops': 'error',
121
+ 'vue/space-unary-ops': ['error', {
122
+ nonwords: false,
123
+ words: true,
124
+ }],
125
+ 'vue/array-bracket-spacing': ['error',
126
+ 'never',
127
+ ],
128
+ 'vue/arrow-spacing': ['error', {
129
+ after: true,
130
+ before: true,
131
+ }],
132
+ 'vue/block-spacing': ['error',
133
+ 'always',
134
+ ],
135
+ 'vue/block-tag-newline': ['error', {
136
+ multiline: 'always',
137
+ singleline: 'always',
138
+ }],
139
+ 'vue/brace-style': ['error',
140
+ 'stroustrup',
141
+ { allowSingleLine: true },
142
+ ],
143
+ 'vue/comma-dangle': ['error',
144
+ 'always-multiline',
145
+ ],
146
+ 'vue/comma-spacing': ['error', {
147
+ after: true,
148
+ before: false,
149
+ }],
150
+ 'vue/comma-style': ['error',
151
+ 'last',
152
+ ],
153
+ 'vue/html-comment-content-spacing': ['error',
154
+ 'always',
155
+ { exceptions: ['-'] },
156
+ ],
157
+ 'vue/key-spacing': ['error', {
158
+ afterColon: true,
159
+ beforeColon: false,
160
+ }],
161
+ 'vue/keyword-spacing': ['error', {
162
+ after: true,
163
+ before: true,
164
+ }],
165
+ 'vue/object-curly-newline': 'off',
166
+ 'vue/object-curly-spacing': ['error',
167
+ 'always',
168
+ ],
169
+ 'vue/object-property-newline': ['error', {
170
+ allowAllPropertiesOnSameLine: true,
171
+ }],
172
+ 'vue/operator-linebreak': ['error',
173
+ 'before',
174
+ ],
175
+ 'vue/padding-line-between-blocks': ['error',
176
+ 'always',
177
+ ],
178
+ 'vue/quote-props': ['error',
179
+ 'consistent-as-needed',
180
+ ],
181
+ 'vue/space-in-parens': ['error',
182
+ 'never',
183
+ ],
184
+ 'vue/template-curly-spacing': 'error',
185
+ 'vue/singleline-html-element-content-newline': ['warn', {
186
+ ignoreWhenNoAttributes: true,
187
+ ignoreWhenEmpty: true,
188
+ }],
189
+ },
22
190
  },
23
- rules: {
24
- // 将 禁止使用null文字 设为警告
25
- 'unicorn/no-null': 'warn',
26
-
27
- // 关闭 vue 代码风格规则
28
- 'vue/html-self-closing': 'off',
29
- 'vue/max-len': 'off',
30
- 'vue/array-bracket-newline': 'off',
31
- 'vue/array-bracket-spacing': 'off',
32
- 'vue/array-element-newline': 'off',
33
- 'vue/arrow-spacing': 'off',
34
- 'vue/block-spacing': 'off',
35
- 'vue/block-tag-newline': 'off',
36
- 'vue/brace-style': 'off',
37
- 'vue/comma-dangle': 'off',
38
- 'vue/comma-spacing': 'off',
39
- 'vue/comma-style': 'off',
40
- 'vue/dot-location': 'off',
41
- 'vue/func-call-spacing': 'off',
42
- 'vue/html-closing-bracket-newline': 'off',
43
- 'vue/html-closing-bracket-spacing': 'off',
44
- 'vue/html-end-tags': 'off',
45
- 'vue/html-indent': 'off',
46
- 'vue/html-quotes': 'off',
47
- 'vue/key-spacing': 'off',
48
- 'vue/keyword-spacing': 'off',
49
- 'vue/max-attributes-per-line': 'off',
50
- 'vue/multiline-html-element-content-newline': 'off',
51
- 'vue/multiline-ternary': 'off',
52
- 'vue/mustache-interpolation-spacing': 'off',
53
- 'vue/no-extra-parens': 'off',
54
- 'vue/no-multi-spaces': 'off',
55
- 'vue/no-spaces-around-equal-signs-in-attribute': 'off',
56
- 'vue/object-curly-newline': 'off',
57
- 'vue/object-curly-spacing': 'off',
58
- 'vue/object-property-newline': 'off',
59
- 'vue/operator-linebreak': 'off',
60
- 'vue/quote-props': 'off',
61
- 'vue/script-indent': 'off',
62
- 'vue/singleline-html-element-content-newline': 'off',
63
- 'vue/space-in-parens': 'off',
64
- 'vue/space-infix-ops': 'off',
65
- 'vue/space-unary-ops': 'off',
66
- 'vue/template-curly-spacing': 'off',
67
- },
68
- },
69
191
 
70
- {
71
- languageOptions: {
72
- parser: tsparser,
73
- parserOptions: {
74
- sourceType: 'module',
75
- ecmaVersion: 'latest',
76
- project: true,
192
+ {
193
+ files: [...defaultJsFiles, ...defaultTsFiles],
194
+ rules: {
195
+ 'vue/one-component-per-file': 'off',
77
196
  },
78
197
  },
79
- },
80
- {
81
- files: ['**/*.vue'],
82
- languageOptions: {
83
- parser: vueparser,
84
- parserOptions: {
85
- sourceType: 'module',
86
- ecmaVersion: 'latest',
87
- parser: {
88
- js: 'espree',
89
- jsx: 'espree',
90
- ts: tsparser,
91
- tsx: tsparser,
198
+
199
+ {
200
+ files: ['**/*.vue'],
201
+ processor: mergeProcessors([
202
+ vue.processors['vue'],
203
+ processorVueBlocks({
204
+ blocks: {
205
+ styles: true,
206
+ customBlocks: false,
207
+ // Usually it's not recommended to lint <script> and <template>
208
+ // As eslint-plugin-vue already provides the support
209
+ script: false,
210
+ template: false,
211
+ },
212
+ }),
213
+ ]),
214
+ languageOptions: {
215
+ parser: vueParser,
216
+ parserOptions: {
217
+ project: true,
218
+ extraFileExtensions,
219
+ sourceType: 'module',
220
+ ecmaVersion: 'latest',
221
+ parser: {
222
+ js: 'espree',
223
+ jsx: 'espree',
224
+ ts: tsParser,
225
+ tsx: tsParser,
226
+ },
92
227
  },
93
- project: true,
228
+ },
229
+ plugins: { tsdoc },
230
+ rules: {
231
+ // 启用 tsdoc 扫描
232
+ 'tsdoc/syntax': 'warn',
233
+ /** script setup 不能开启此规则 */
234
+ 'import-x/exports-last': 'off',
235
+ // 不允许使用标记为 @deprecated 的代码
236
+ 'ts/no-deprecated': 'warn',
94
237
  },
95
238
  },
96
- rules: {
97
- // 启用 tsdoc 扫描
98
- 'tsdoc/syntax': 'warn',
99
239
 
100
- // 不允许使用标记为 @deprecated 的代码
101
- '@typescript-eslint/no-deprecated': ['warn'],
240
+ {
241
+ files: ['**/tests/unit/**'],
242
+ ...vitest.configs['recommended'],
102
243
  },
103
- },
104
-
105
- {
106
- files: ['tests/unit/**'],
107
- ...vitest.configs['recommended'],
108
- },
109
- {
110
- files: ['tests/e2e/**'],
111
- ...playwright.configs['flat/recommended'],
112
- },
113
244
 
114
- disableJsTypeChecked,
245
+ {
246
+ files: ['**/tests/e2e/**'],
247
+ ...playwright.configs['flat/recommended'],
248
+ },
115
249
 
116
- withUnoCSS ? unocss : {},
117
- )
250
+ withUnoCSS ? unocss : {},
251
+ disableTypeChecked,
252
+ ).renamePlugins({
253
+ vitest: 'test',
254
+ })
118
255
  }
@@ -0,0 +1,26 @@
1
+ import { noRelativeParentImportRule } from './no-relative-parent-imports.js'
2
+ import { preferImportAliasRule } from './prefer-import-alias.js'
3
+
4
+ const rules = {
5
+ 'no-relative-parent-imports': noRelativeParentImportRule,
6
+ 'prefer-import-alias': preferImportAliasRule,
7
+ }
8
+
9
+ const configs = {
10
+ recommended: {
11
+ plugins: {
12
+ '@ithinkdt': { rules },
13
+ },
14
+ rules: {
15
+ '@ithinkdt/no-relative-parent-imports': 'warn',
16
+ '@ithinkdt/prefer-import-alias': 'warn',
17
+ },
18
+ },
19
+ }
20
+
21
+ const plugin = {
22
+ rules,
23
+ configs,
24
+ }
25
+
26
+ export default plugin
@@ -0,0 +1,54 @@
1
+ import path from 'node:path'
2
+
3
+ export const noRelativeParentImportRule = {
4
+ meta: {
5
+ type: 'suggestion',
6
+ hasSuggestions: true,
7
+ docs: {
8
+ description: 'Suggests import use project path',
9
+ category: 'Suggestions',
10
+ recommended: true,
11
+ suggestion: true,
12
+ },
13
+ },
14
+
15
+ create: (context) => {
16
+ const aliasModuleDeclarations = (node) => {
17
+ const importModuleName = node.source?.value
18
+ if (
19
+ !node.source?.range
20
+ || typeof importModuleName !== 'string'
21
+ || importModuleName[0] !== '.'
22
+ || importModuleName[1] !== '.'
23
+ )
24
+ return
25
+
26
+ const moduleFilename = path
27
+ .resolve(path.dirname(context.filename), importModuleName)
28
+ .slice(context.cwd.length + 1)
29
+
30
+ return context.report({
31
+ node,
32
+ message: `relative parent import not suggested`,
33
+ suggest: [
34
+ {
35
+ desc: `replace '${importModuleName}' with '${moduleFilename}'`,
36
+ fix: (fixer) => {
37
+ return fixer.replaceTextRange(
38
+ [node.source.range[0] + 1, node.source.range[1] - 1],
39
+ moduleFilename,
40
+ )
41
+ },
42
+ },
43
+ ],
44
+ })
45
+ }
46
+
47
+ return {
48
+ ExportAllDeclaration: aliasModuleDeclarations,
49
+ ExportNamedDeclaration: aliasModuleDeclarations,
50
+ ImportDeclaration: aliasModuleDeclarations,
51
+ ImportExpression: aliasModuleDeclarations,
52
+ }
53
+ },
54
+ }
@@ -0,0 +1,66 @@
1
+ const getAliasSuggestion = (importModuleName, range, pathAlias) => {
2
+ if (!range || typeof importModuleName !== 'string' || importModuleName[0] === '.' || importModuleName[0] === '/')
3
+ return
4
+
5
+ let matchedAlias
6
+ for (const alias of pathAlias) {
7
+ if (
8
+ (importModuleName === alias[1] || importModuleName.startsWith(alias[1] + '/'))
9
+ && (!matchedAlias || matchedAlias.length < alias[0].length)
10
+ ) {
11
+ matchedAlias = alias
12
+ }
13
+ }
14
+ if (!matchedAlias) return
15
+
16
+ const aliasSuggestion = matchedAlias[0] + importModuleName.slice(matchedAlias[1].length)
17
+
18
+ if (aliasSuggestion) {
19
+ return {
20
+ message: `import ${importModuleName} can be written as ${aliasSuggestion}`,
21
+ suggest: [
22
+ {
23
+ desc: `replace '${importModuleName}' with '${aliasSuggestion}'`,
24
+ fix: (fixer) => {
25
+ return fixer.replaceTextRange([range[0] + 1, range[1] - 1], aliasSuggestion)
26
+ },
27
+ },
28
+ ],
29
+ }
30
+ }
31
+ }
32
+
33
+ export const preferImportAliasRule = {
34
+ meta: {
35
+ type: 'suggestion',
36
+ hasSuggestions: true,
37
+ docs: {
38
+ description: 'Suggests shortest matching alias',
39
+ category: 'Suggestions',
40
+ recommended: true,
41
+ suggestion: true,
42
+ },
43
+ },
44
+
45
+ create: (context) => {
46
+ const pathAlias = (context.settings['import/resolver'] ?? context.settings['import-x/resolver'])?.['custom-alias']?.alias
47
+
48
+ if (!pathAlias) {
49
+ return {}
50
+ }
51
+
52
+ const aliasModuleDeclarations = (node) => {
53
+ const problem = getAliasSuggestion(node.source?.value, node.source?.range, Object.entries(pathAlias))
54
+ if (problem) {
55
+ context.report({ ...problem, node })
56
+ }
57
+ }
58
+
59
+ return {
60
+ ExportAllDeclaration: aliasModuleDeclarations,
61
+ ExportNamedDeclaration: aliasModuleDeclarations,
62
+ ImportDeclaration: aliasModuleDeclarations,
63
+ ImportExpression: aliasModuleDeclarations,
64
+ }
65
+ },
66
+ }
@@ -1,4 +1,4 @@
1
1
  import { Config } from 'stylelint'
2
2
 
3
- declare const config: Config
3
+ declare const config: (config?: Config) => Config
4
4
  export default config
package/src/stylelint.js CHANGED
@@ -1,49 +1,25 @@
1
- import order from 'stylelint-order'
2
-
3
- import scss from 'stylelint-config-standard-scss'
1
+ import std from 'stylelint-config-standard'
4
2
  import vue from 'stylelint-config-standard-vue'
5
- import vuescss from 'stylelint-config-standard-vue/scss/index.js'
6
-
7
- import postcsshtml from 'postcss-html'
8
- import postcssscss from 'postcss-scss'
3
+ import order from 'stylelint-order'
9
4
 
10
5
  /**
11
6
  * @type {import('stylelint').Config}
12
7
  */
13
- export default {
14
- extends: [scss, vue, vuescss],
15
- plugins: [...order],
16
- overrides: [
17
- {
18
- files: ['**/*.(scss|css|vue|html)'],
19
- customSyntax: postcssscss,
20
- },
21
- {
22
- files: ['**/*.(html|vue)'],
23
- customSyntax: postcsshtml(),
8
+ export default function config(config = {}) {
9
+ return {
10
+ extends: [std, vue, config],
11
+ plugins: [...order],
12
+ ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml'],
13
+ rules: {
14
+ 'selector-class-pattern': [
15
+ /^([a-z][\da-z]*)((-{1,2}|_{1,2})[\da-z]+)*$/,
16
+ {
17
+ message: selector => `Expected class selector "${selector}" to be kebab-case or bem`,
18
+ },
19
+ ],
20
+ 'custom-property-empty-line-before': [
21
+ 'never',
22
+ ],
24
23
  },
25
- ],
26
- ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml'],
27
- rules: {
28
- 'no-descending-specificity': [false],
29
- 'selector-pseudo-element-no-unknown': [
30
- true,
31
- {
32
- ignorePseudoElements: ['v-deep'],
33
- },
34
- ],
35
- 'selector-pseudo-class-no-unknown': [
36
- true,
37
- {
38
- ignorePseudoClasses: ['deep'],
39
- },
40
- ],
41
-
42
- 'selector-class-pattern': [
43
- /^([a-z][\da-z]*)((-{1,2}|_{1,2})[\da-z]+)*$/,
44
- {
45
- message: (selector) => `Expected class selector "${selector}" to be kebab-case or bem`,
46
- },
47
- ],
48
- },
24
+ }
49
25
  }
@@ -1,13 +1,12 @@
1
1
  import { parser as tsEslintParser } from 'typescript-eslint'
2
2
 
3
3
  import { TSServiceManager } from './ts.js'
4
-
5
4
  import { getProjectConfigFiles } from './utils/get-project-config-files.js'
6
5
  import { resolveProjectList } from './utils/resolve-project-list.js'
7
6
 
8
7
  export const meta = {
9
8
  name: 'typescript-eslint-parser-for-extra-files',
10
- version: '0.7.0',
9
+ version: '0.8.0',
11
10
  }
12
11
 
13
12
  const DEFAULT_EXTRA_FILE_EXTENSIONS = ['.vue']
@@ -19,7 +18,7 @@ export function parseForESLint(code, options = {}) {
19
18
  }
20
19
  const extraFileExtensions = options.extraFileExtensions || DEFAULT_EXTRA_FILE_EXTENSIONS
21
20
  const programs = []
22
- for (const option of iterateOptions(options)) {
21
+ for (const option of _iterateOptions(options)) {
23
22
  programs.push(tsServiceManager.getProgram(code, option))
24
23
  }
25
24
  const filePath = options.filePath
@@ -32,7 +31,7 @@ export function parseForESLint(code, options = {}) {
32
31
  return tsEslintParser.parseForESLint(code, parserOptions)
33
32
  }
34
33
 
35
- function* iterateOptions(options) {
34
+ function* _iterateOptions(options) {
36
35
  if (!options) {
37
36
  throw new Error('`parserOptions` is required.')
38
37
  }
@@ -1,20 +1,8 @@
1
1
  import path from 'node:path'
2
2
 
3
- import { parse, compileScript } from '@vue/compiler-sfc'
3
+ import { compileScript, parse } from '@vue/compiler-sfc'
4
4
 
5
- export function transformExtraFile(code, context) {
6
- const ext = path.extname(context.filePath)
7
- const transform = ext === '.vue' ? transformForVue : () => code
8
- try {
9
- return transform(code, context)
10
- } catch {
11
- // ignore
12
- }
13
-
14
- return code
15
- }
16
-
17
- function transformForVue(code, context) {
5
+ function _transformForVue(code, context) {
18
6
  if (context.current) {
19
7
  return code
20
8
  }
@@ -26,3 +14,15 @@ function transformForVue(code, context) {
26
14
 
27
15
  return compiled.content
28
16
  }
17
+
18
+ export function transformExtraFile(code, context) {
19
+ const ext = path.extname(context.filePath)
20
+ const transform = ext === '.vue' ? _transformForVue : () => code
21
+ try {
22
+ return transform(code, context)
23
+ } catch {
24
+ // ignore
25
+ }
26
+
27
+ return code
28
+ }