@ithinkdt/lint 4.0.0-6 → 4.0.0-700

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,249 @@
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 processorVueBlocks from 'eslint-processor-vue-blocks'
4
6
 
5
- import base, { disableJsTypeChecked, tsparser, vueparser, vue, vitest, playwright } from './base.js'
7
+ import { base, defaultEsFiles, defaultJsFiles, defaultTsFiles, disableTypeChecked, playwright, tsParser, vitest, vue, vueParser } from './base.js'
8
+ import ithinkdt from './rules/index.js'
6
9
 
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: {
10
+ export * from './base.js'
11
+
12
+ export default function config({ unocss: withUnoCSS, stylistic: withStylistic, tsconfigRootDir } = {}) {
13
+ const extraFileExtensions = ['.vue']
14
+
15
+ withStylistic = withStylistic === true
16
+ ? {
17
+ indent: 4,
18
+ quotes: 'single',
19
+ semi: false,
20
+ jsx: true,
21
+ braceStyle: '1tbs',
22
+ }
23
+ : withStylistic
24
+
25
+ return base({ inCI: !!env.CI && env.CI !== 'false', stylistic: withStylistic })
26
+ .append(
27
+ ithinkdt.configs.recommended,
28
+ vue.configs['flat/recommended'].map((it) => {
29
+ return {
30
+ ...it,
31
+ files: defaultEsFiles,
32
+ }
33
+ }),
34
+ {
35
+ files: defaultEsFiles,
36
+ languageOptions: {
37
+ parser: tsParser,
38
+ parserOptions: {
19
39
  project: true,
40
+ tsconfigRootDir,
41
+ sourceType: 'module',
42
+ ecmaVersion: 'latest',
20
43
  },
21
44
  },
45
+ rules: {
46
+ 'vue/block-lang': ['warn', {
47
+ script: {
48
+ lang: ['ts', 'tsx'],
49
+ },
50
+ }],
51
+ 'vue/block-order': ['error', {
52
+ order: ['script', 'template', 'style'],
53
+ }],
54
+ 'vue/component-name-in-template-casing': ['error',
55
+ 'PascalCase',
56
+ ],
57
+ 'vue/component-options-name-casing': ['error',
58
+ 'PascalCase',
59
+ ],
60
+ // this is deprecated
61
+ 'vue/component-tags-order': 'off',
62
+ 'vue/custom-event-name-casing': ['error',
63
+ 'camelCase',
64
+ ],
65
+ 'vue/define-macros-order': ['warn', {
66
+ order: ['definePage', 'defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots'],
67
+ }],
68
+ 'vue/dot-location': ['error',
69
+ 'property',
70
+ ],
71
+ 'vue/dot-notation': ['error', {
72
+ allowKeywords: true,
73
+ }],
74
+ 'vue/eqeqeq': ['error',
75
+ 'smart',
76
+ ],
77
+ 'vue/no-mutating-props': ['error', {
78
+ shallowOnly: true,
79
+ }],
80
+ 'vue/html-indent': ['error',
81
+ withStylistic?.indent,
82
+ ],
83
+ 'vue/html-quotes': ['error',
84
+ 'double',
85
+ ],
86
+ 'vue/max-attributes-per-line': 'off',
87
+ 'vue/multi-word-component-names': 'off',
88
+ 'vue/no-dupe-keys': 'off',
89
+ 'vue/no-empty-pattern': 'error',
90
+ 'vue/no-irregular-whitespace': 'error',
91
+ 'vue/no-loss-of-precision': 'error',
92
+ 'vue/no-restricted-syntax': ['error',
93
+ 'DebuggerStatement',
94
+ 'LabeledStatement',
95
+ 'WithStatement',
96
+ ],
97
+ 'vue/no-restricted-v-bind': ['error',
98
+ '/^v-/',
99
+ ],
100
+ 'vue/no-setup-props-reactivity-loss': 'off',
101
+ 'vue/no-sparse-arrays': 'error',
102
+ 'vue/no-unused-refs': 'error',
103
+ 'vue/no-useless-v-bind': 'error',
104
+ 'vue/no-v-html': 'off',
105
+ 'vue/object-shorthand': ['error',
106
+ 'always',
107
+ {
108
+ avoidQuotes: true,
109
+ ignoreConstructors: false,
110
+ },
111
+ ],
112
+ 'vue/prefer-separate-static-class': 'error',
113
+ 'vue/prefer-template': 'error',
114
+ 'vue/prop-name-casing': ['error',
115
+ 'camelCase',
116
+ ],
117
+ 'vue/require-default-prop': 'off',
118
+ 'vue/require-prop-types': 'off',
119
+ 'vue/space-infix-ops': 'error',
120
+ 'vue/space-unary-ops': ['error', {
121
+ nonwords: false,
122
+ words: true,
123
+ }],
124
+ 'vue/array-bracket-spacing': ['error',
125
+ 'never',
126
+ ],
127
+ 'vue/arrow-spacing': ['error', {
128
+ after: true,
129
+ before: true,
130
+ }],
131
+ 'vue/block-spacing': ['error',
132
+ 'always',
133
+ ],
134
+ 'vue/block-tag-newline': ['error', {
135
+ multiline: 'always',
136
+ singleline: 'always',
137
+ }],
138
+ 'vue/brace-style': ['error',
139
+ 'stroustrup',
140
+ { allowSingleLine: true },
141
+ ],
142
+ 'vue/comma-dangle': ['error',
143
+ 'always-multiline',
144
+ ],
145
+ 'vue/comma-spacing': ['error', {
146
+ after: true,
147
+ before: false,
148
+ }],
149
+ 'vue/comma-style': ['error',
150
+ 'last',
151
+ ],
152
+ 'vue/html-comment-content-spacing': ['error',
153
+ 'always',
154
+ { exceptions: ['-'] },
155
+ ],
156
+ 'vue/key-spacing': ['error', {
157
+ afterColon: true,
158
+ beforeColon: false,
159
+ }],
160
+ 'vue/keyword-spacing': ['error', {
161
+ after: true,
162
+ before: true,
163
+ }],
164
+ 'vue/object-curly-newline': 'off',
165
+ 'vue/object-curly-spacing': ['error',
166
+ 'always',
167
+ ],
168
+ 'vue/object-property-newline': ['error', {
169
+ allowAllPropertiesOnSameLine: true,
170
+ }],
171
+ 'vue/operator-linebreak': ['error',
172
+ 'before',
173
+ ],
174
+ 'vue/padding-line-between-blocks': ['error',
175
+ 'always',
176
+ ],
177
+ 'vue/quote-props': ['error',
178
+ 'consistent-as-needed',
179
+ ],
180
+ 'vue/space-in-parens': ['error',
181
+ 'never',
182
+ ],
183
+ 'vue/template-curly-spacing': 'error',
184
+ 'vue/singleline-html-element-content-newline': ['warn', {
185
+ ignoreWhenNoAttributes: true,
186
+ ignoreWhenEmpty: true,
187
+ }],
188
+ },
22
189
  },
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
190
 
70
- {
71
- languageOptions: {
72
- parser: tsparser,
73
- parserOptions: {
74
- sourceType: 'module',
75
- ecmaVersion: 'latest',
76
- project: true,
191
+ {
192
+ files: [...defaultJsFiles, ...defaultTsFiles],
193
+ rules: {
194
+ 'vue/one-component-per-file': 'off',
77
195
  },
78
196
  },
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,
197
+
198
+ {
199
+ files: ['**/*.vue'],
200
+ processor: mergeProcessors([
201
+ vue.processors['vue'],
202
+ processorVueBlocks({
203
+ blocks: {
204
+ styles: true,
205
+ customBlocks: false,
206
+ // Usually it's not recommended to lint <script> and <template>
207
+ // As eslint-plugin-vue already provides the support
208
+ script: false,
209
+ template: false,
210
+ },
211
+ }),
212
+ ]),
213
+ languageOptions: {
214
+ parser: vueParser,
215
+ parserOptions: {
216
+ project: true,
217
+ extraFileExtensions,
218
+ sourceType: 'module',
219
+ ecmaVersion: 'latest',
220
+ parser: {
221
+ js: 'espree',
222
+ jsx: 'espree',
223
+ ts: tsParser,
224
+ tsx: tsParser,
225
+ },
92
226
  },
93
- project: true,
227
+ },
228
+ rules: {
229
+ /** script setup 不能开启此规则 */
230
+ 'import-x/exports-last': 'off',
94
231
  },
95
232
  },
96
- rules: {
97
- // 启用 tsdoc 扫描
98
- 'tsdoc/syntax': 'warn',
99
233
 
100
- // 不允许使用标记为 @deprecated 的代码
101
- '@typescript-eslint/no-deprecated': ['warn'],
234
+ {
235
+ files: ['**/tests/unit/**'],
236
+ ...vitest.configs['recommended'],
102
237
  },
103
- },
104
-
105
- {
106
- files: ['tests/unit/**'],
107
- ...vitest.configs['recommended'],
108
- },
109
- {
110
- files: ['tests/e2e/**'],
111
- ...playwright.configs['flat/recommended'],
112
- },
113
238
 
114
- disableJsTypeChecked,
239
+ {
240
+ files: ['**/tests/e2e/**'],
241
+ ...playwright.configs['flat/recommended'],
242
+ },
115
243
 
116
- withUnoCSS ? unocss : {},
117
- )
244
+ withUnoCSS ? unocss : {},
245
+ disableTypeChecked,
246
+ ).renamePlugins({
247
+ vitest: 'test',
248
+ })
118
249
  }
@@ -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,14 @@
1
+ import { cwd } from 'node:process'
2
+
1
3
  import { parser as tsEslintParser } from 'typescript-eslint'
2
4
 
3
5
  import { TSServiceManager } from './ts.js'
4
-
5
6
  import { getProjectConfigFiles } from './utils/get-project-config-files.js'
6
7
  import { resolveProjectList } from './utils/resolve-project-list.js'
7
8
 
8
9
  export const meta = {
9
10
  name: 'typescript-eslint-parser-for-extra-files',
10
- version: '0.7.0',
11
+ version: '0.8.0',
11
12
  }
12
13
 
13
14
  const DEFAULT_EXTRA_FILE_EXTENSIONS = ['.vue']
@@ -19,7 +20,7 @@ export function parseForESLint(code, options = {}) {
19
20
  }
20
21
  const extraFileExtensions = options.extraFileExtensions || DEFAULT_EXTRA_FILE_EXTENSIONS
21
22
  const programs = []
22
- for (const option of iterateOptions(options)) {
23
+ for (const option of _iterateOptions(options)) {
23
24
  programs.push(tsServiceManager.getProgram(code, option))
24
25
  }
25
26
  const filePath = options.filePath
@@ -32,7 +33,7 @@ export function parseForESLint(code, options = {}) {
32
33
  return tsEslintParser.parseForESLint(code, parserOptions)
33
34
  }
34
35
 
35
- function* iterateOptions(options) {
36
+ function* _iterateOptions(options) {
36
37
  if (!options) {
37
38
  throw new Error('`parserOptions` is required.')
38
39
  }
@@ -42,7 +43,7 @@ function* iterateOptions(options) {
42
43
  if (!options.project) {
43
44
  throw new Error('Specify `parserOptions.project`. Otherwise there is no point in using this parser.')
44
45
  }
45
- const tsconfigRootDir = typeof options.tsconfigRootDir === 'string' ? options.tsconfigRootDir : process.cwd()
46
+ const tsconfigRootDir = typeof options.tsconfigRootDir === 'string' ? options.tsconfigRootDir : cwd()
46
47
 
47
48
  for (const project of resolveProjectList({
48
49
  project: getProjectConfigFiles({
@@ -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
+ }