@ithinkdt/lint 4.0.0-2 → 4.0.0-201

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,100 +1,255 @@
1
- import { config as tsconfig } from 'typescript-eslint'
2
- import vue from 'eslint-plugin-vue'
1
+ import { env } from 'node:process'
3
2
 
4
- import base, { disableJsTypeChecked, tsparser, vueparser } from './base.js'
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'
5
7
 
6
- export default tsconfig(
7
- ...base,
8
- ...vue.configs['flat/recommended'],
9
- {
10
- settings: {
11
- 'import-x/resolver': {
12
- 'custom-alias': {
13
- alias: { '@': './src' },
14
- extensions: ['.js', '.jsx', '.ts', 'tsx'],
8
+ import { base, defaultEsFiles, defaultJsFiles, defaultTsFiles, disableTypeChecked, playwright, tsParser, vitest, vue, vueParser } from './base.js'
9
+ import ithinkdt from './rules/index.js'
10
+
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: {
40
+ project: true,
41
+ tsconfigRootDir,
42
+ sourceType: 'module',
43
+ ecmaVersion: 'latest',
44
+ },
15
45
  },
16
- typescript: {
17
- project: true,
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
+ }],
18
189
  },
19
190
  },
20
- },
21
- rules: {
22
- // 关闭 vue 代码风格规则
23
- 'vue/html-self-closing': 'off',
24
- 'vue/max-len': 'off',
25
- 'vue/array-bracket-newline': 'off',
26
- 'vue/array-bracket-spacing': 'off',
27
- 'vue/array-element-newline': 'off',
28
- 'vue/arrow-spacing': 'off',
29
- 'vue/block-spacing': 'off',
30
- 'vue/block-tag-newline': 'off',
31
- 'vue/brace-style': 'off',
32
- 'vue/comma-dangle': 'off',
33
- 'vue/comma-spacing': 'off',
34
- 'vue/comma-style': 'off',
35
- 'vue/dot-location': 'off',
36
- 'vue/func-call-spacing': 'off',
37
- 'vue/html-closing-bracket-newline': 'off',
38
- 'vue/html-closing-bracket-spacing': 'off',
39
- 'vue/html-end-tags': 'off',
40
- 'vue/html-indent': 'off',
41
- 'vue/html-quotes': 'off',
42
- 'vue/key-spacing': 'off',
43
- 'vue/keyword-spacing': 'off',
44
- 'vue/max-attributes-per-line': 'off',
45
- 'vue/multiline-html-element-content-newline': 'off',
46
- 'vue/multiline-ternary': 'off',
47
- 'vue/mustache-interpolation-spacing': 'off',
48
- 'vue/no-extra-parens': 'off',
49
- 'vue/no-multi-spaces': 'off',
50
- 'vue/no-spaces-around-equal-signs-in-attribute': 'off',
51
- 'vue/object-curly-newline': 'off',
52
- 'vue/object-curly-spacing': 'off',
53
- 'vue/object-property-newline': 'off',
54
- 'vue/operator-linebreak': 'off',
55
- 'vue/quote-props': 'off',
56
- 'vue/script-indent': 'off',
57
- 'vue/singleline-html-element-content-newline': 'off',
58
- 'vue/space-in-parens': 'off',
59
- 'vue/space-infix-ops': 'off',
60
- 'vue/space-unary-ops': 'off',
61
- 'vue/template-curly-spacing': 'off',
62
- },
63
- },
64
191
 
65
- {
66
- languageOptions: {
67
- parser: tsparser,
68
- parserOptions: {
69
- sourceType: 'module',
70
- ecmaVersion: 'latest',
71
- project: true,
192
+ {
193
+ files: [...defaultJsFiles, ...defaultTsFiles],
194
+ rules: {
195
+ 'vue/one-component-per-file': 'off',
196
+ },
72
197
  },
73
- },
74
- },
75
- {
76
- files: ['**/*.vue'],
77
- languageOptions: {
78
- parser: vueparser,
79
- parserOptions: {
80
- sourceType: 'module',
81
- ecmaVersion: 'latest',
82
- parser: {
83
- js: 'espree',
84
- jsx: 'espree',
85
- ts: tsparser,
86
- 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
+ },
227
+ },
87
228
  },
88
- project: true,
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',
237
+ },
238
+ },
239
+
240
+ {
241
+ files: ['**/tests/unit/**'],
242
+ ...vitest.configs['recommended'],
243
+ },
244
+
245
+ {
246
+ files: ['**/tests/e2e/**'],
247
+ ...playwright.configs['flat/recommended'],
89
248
  },
90
- },
91
- rules: {
92
- // 启用 tsdoc 扫描
93
- 'tsdoc/syntax': 'warn',
94
249
 
95
- // 启用 已弃用API 扫描
96
- 'deprecation/deprecation': 'error',
97
- },
98
- },
99
- disableJsTypeChecked,
100
- )
250
+ withUnoCSS ? unocss : {},
251
+ disableTypeChecked,
252
+ ).renamePlugins({
253
+ vitest: 'test',
254
+ })
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
+ }