eslint-plugin-smarthr 0.2.0 → 0.2.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +15 -574
  3. package/index.js +3 -6
  4. package/package.json +1 -1
  5. package/rules/a11y-clickable-element-has-text/README.md +61 -0
  6. package/rules/{a11y-clickable-element-has-text.js → a11y-clickable-element-has-text/index.js} +1 -1
  7. package/rules/a11y-image-has-alt-attribute/README.md +55 -0
  8. package/rules/{a11y-image-has-alt-attribute.js → a11y-image-has-alt-attribute/index.js} +1 -1
  9. package/rules/a11y-trigger-has-button/README.md +57 -0
  10. package/rules/{a11y-trigger-has-button.js → a11y-trigger-has-button/index.js} +1 -1
  11. package/rules/best-practice-for-date/README.md +40 -0
  12. package/rules/best-practice-for-date/index.js +42 -0
  13. package/rules/format-import-path/README.md +99 -0
  14. package/rules/{format-import-path.js → format-import-path/index.js} +2 -2
  15. package/rules/format-translate-component/README.md +58 -0
  16. package/rules/{format-translate-component.js → format-translate-component/index.js} +0 -0
  17. package/rules/jsx-start-with-spread-attributes/README.md +31 -0
  18. package/rules/{jsx-start-with-spread-attributes.js → jsx-start-with-spread-attributes/index.js} +0 -0
  19. package/rules/no-import-other-domain/README.md +85 -0
  20. package/rules/{no-import-other-domain.js → no-import-other-domain/index.js} +2 -2
  21. package/rules/prohibit-export-array-type/README.md +28 -0
  22. package/rules/prohibit-export-array-type/index.js +28 -0
  23. package/rules/prohibit-file-name/README.md +35 -0
  24. package/rules/prohibit-file-name/index.js +61 -0
  25. package/rules/prohibit-import/README.md +44 -0
  26. package/rules/{prohibit-import.js → prohibit-import/index.js} +0 -0
  27. package/rules/redundant-name/README.md +94 -0
  28. package/rules/{redundant-name.js → redundant-name/index.js} +1 -1
  29. package/rules/require-barrel-import/README.md +39 -0
  30. package/rules/{require-barrel-import.js → require-barrel-import/index.js} +1 -1
  31. package/rules/require-export/README.md +43 -0
  32. package/rules/require-export/index.js +90 -0
  33. package/rules/require-import/README.md +51 -0
  34. package/rules/{require-import.js → require-import/index.js} +0 -0
  35. package/test/best-practice-for-date.js +31 -0
  36. package/test/prohibit-file-name.js +45 -0
  37. package/test/require-export.js +83 -0
@@ -0,0 +1,44 @@
1
+ # smarthr/prohibit-import
2
+
3
+ - 例えば特定の module にバグが発見されたなど、importさせたくない場合に利用するルールです
4
+
5
+ ## rules
6
+
7
+ ```js
8
+ {
9
+ rules: {
10
+ 'smarthr/prohibit-import': [
11
+ 'error', // 'warn', 'off'
12
+ {
13
+ '^.+$': {
14
+ 'smarthr-ui': {
15
+ imported: ['SecondaryButtonAnchor'],
16
+ reportMessage: `{{module}}/{{export}} はXxxxxxなので利用せず yyyy/zzzz を利用してください`
17
+ },
18
+ }
19
+ '\/pages\/views\/': {
20
+ 'query-string': {
21
+ imported: true,
22
+ },
23
+ },
24
+ }
25
+ ]
26
+ },
27
+ }
28
+ ```
29
+
30
+ ## ❌ Incorrect
31
+
32
+ ```js
33
+ // src/pages/views/Page.tsx
34
+ import queryString from 'query-string'
35
+ import { SecondaryButtonAnchor } from 'smarthr-ui'
36
+ ```
37
+
38
+ ## ✅ Correct
39
+
40
+
41
+ ```js
42
+ // src/pages/views/Page.tsx
43
+ import { PrimaryButton, SecondaryButton } from 'smarthr-ui'
44
+ ```
@@ -0,0 +1,94 @@
1
+ # smarthr/redundant-name
2
+
3
+ - ファイル、コードの冗長な部分を取り除くことを提案するruleです
4
+ - ファイルが設置されているディレクトリ構造からキーワードを生成し、取り除く文字列を生成します
5
+
6
+ ## config
7
+
8
+ - tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
9
+ - 以下の設定を行えます。全て省略可能です。
10
+ - ignoreKeywords
11
+ - ディレクトリ名から生成されるキーワードに含めたくない文字列を指定します
12
+ - betterNames
13
+ - 対象の名前を修正する候補を指定します
14
+ - allowedNames
15
+ - 許可する名前を指定します
16
+ - suffix:
17
+ - type のみ指定出来ます
18
+ - type のsuffixを指定します
19
+
20
+ ### ファイル例
21
+ - `@/crews/index/views/page.tsx` の場合
22
+ - 生成されるキーワードは `['crews', 'crew', 'index', 'page']`
23
+ - `@/crews/index/views/parts/Abc.tsx` の場合
24
+ - 生成されるキーワードは `['crews', 'crew', 'index', 'Abc']`
25
+ - `@/crews/index/repositories/index.ts` の場合
26
+ - 生成されるキーワードは `['crews', 'crew', 'index', 'repositories', 'repository']`
27
+
28
+
29
+ ## rules
30
+
31
+ ```js
32
+ const ignorekeywords = ['views', 'parts']
33
+ const betterNames = {
34
+ '\/repositories\/': {
35
+ operator: '-',
36
+ names: ['repository', 'Repository'],
37
+ },
38
+ '\/entities\/': {
39
+ operator: '+',
40
+ names: ['entity'],
41
+ },
42
+ '\/slices\/': {
43
+ operator: '=',
44
+ names: ['index'],
45
+ },
46
+ }
47
+ // const allowedNames = {
48
+ // '\/views\/crews\/histories\/': ['crewId'],
49
+ // }
50
+
51
+ {
52
+ rules: {
53
+ 'smarthr/redundant-name': [
54
+ 'error', // 'warn', 'off'
55
+ {
56
+ type: { ignorekeywords, suffix: ['Props', 'Type'] },
57
+ file: { ignorekeywords, betternames },
58
+ // property: { ignorekeywords, allowedNames },
59
+ // function: { ignorekeywords },
60
+ // variable: { ignorekeywords },
61
+ // class: { ignorekeywords },
62
+ }
63
+ ]
64
+ },
65
+ }
66
+ ```
67
+
68
+ ## ❌ Incorrect
69
+
70
+ ```js
71
+ // @/crews/index/views/page.tsx
72
+
73
+ type CrewIndexPage = { hoge: string }
74
+ type CrewsView = { hoge: string }
75
+ ```
76
+ ```js
77
+ // @/crews/show/repositories/index.tsx
78
+
79
+ type CrewIndexRepository = { hoge: () => any }
80
+ ```
81
+
82
+ ## ✅ Correct
83
+
84
+ ```js
85
+ // @/crews/index/views/page.tsx
86
+
87
+ type ItemProps = { hoge: string }
88
+ ```
89
+ ```js
90
+ // @/crews/show/repositories/index.tsx
91
+
92
+ type IndexProps = { hoge: () => any }
93
+ type ResponseType = { hoge: () => any }
94
+ ```
@@ -1,7 +1,7 @@
1
1
  const path = require('path')
2
2
  const Inflector = require('inflected')
3
3
 
4
- const { rootPath } = require('../libs/common')
4
+ const { rootPath } = require('../../libs/common')
5
5
 
6
6
  const uniq = (array) => array.filter((elem, index, self) => self.indexOf(elem) === index)
7
7
 
@@ -0,0 +1,39 @@
1
+ # smarthr/require-barrel-import
2
+
3
+ - tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
4
+ - importした対象が本来exportされているべきであるbarrel(index.tsなど)が有る場合、import pathの変更を促します
5
+ - 例: Page/parts/Menu/Item の import は Page/parts/Menu から行わせたい
6
+ - ディレクトリ内のindexファイルを捜査し、対象を決定します
7
+
8
+ ## rules
9
+
10
+ ```js
11
+ {
12
+ rules: {
13
+ 'smarthr/require-barrel-import': 'error',
14
+ },
15
+ }
16
+ ```
17
+
18
+ ## ❌ Incorrect
19
+
20
+ ```js
21
+ // client/src/views/Page/parts/Menu/index.ts
22
+ export { Menu } from './Menu'
23
+ export { Item } from './Item'
24
+
25
+ // client/src/App.tsx
26
+ import { Item } from './Page/parts/Menu/Item'
27
+ ```
28
+
29
+ ## ✅ Correct
30
+
31
+
32
+ ```js
33
+ // client/src/views/Page/parts/Menu/index.ts
34
+ export { Menu } from './Menu'
35
+ export { Item } from './Item'
36
+
37
+ // client/src/App.tsx
38
+ import { Item } from './Page/parts/Menu'
39
+ ```
@@ -1,6 +1,6 @@
1
1
  const path = require('path')
2
2
  const fs = require('fs')
3
- const { replacePaths, rootPath } = require('../libs/common')
3
+ const { replacePaths, rootPath } = require('../../libs/common')
4
4
  const calculateAbsoluteImportPath = (source) => {
5
5
  if (source[0] === '/') {
6
6
  return source
@@ -0,0 +1,43 @@
1
+ # smarthr/require-export
2
+
3
+ - 対象ファイルにexportを強制させたい場合に利用します
4
+ - 例: Page.tsx ではページタイトルを設定させたいので useTitle を必ずexportさせたい
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/require-export': [
12
+ 'error',
13
+ {
14
+ 'adapter\/.+\.ts': ['Props', 'generator'],
15
+ // slice以下のファイルかつmodulesディレクトリに属さずファイル名にmockを含まないもの
16
+ '^(?=.*\/slices\/[a-zA-Z0-9]+\.ts)(?!.*(\/modules\/|mock\.)).*$': [ 'default' ],
17
+ },
18
+ ]
19
+ },
20
+ }
21
+ ```
22
+
23
+ ## ❌ Incorrect
24
+
25
+ ```js
26
+ // adapter/index.ts
27
+ export type Type = { abc: string }
28
+
29
+ // slice/index.ts
30
+ export const slice = { method: () => 'any action' }
31
+ ```
32
+
33
+ ## ✅ Correct
34
+
35
+
36
+ ```js
37
+ // adapter/index.ts
38
+ export type Props = { abc: string }
39
+
40
+ // slice/index.ts
41
+ const slice = { method: () => 'any action' }
42
+ export default slice
43
+ ```
@@ -0,0 +1,90 @@
1
+ const SCHEMA = [{
2
+ type: 'object',
3
+ patternProperties: {
4
+ '.+': {
5
+ type: 'array',
6
+ items: { type: 'string' },
7
+ additionalProperties: true,
8
+ },
9
+ },
10
+ additionalProperties: true,
11
+ }]
12
+
13
+ const fetchEdgeDeclaration = (node) => {
14
+ const { declaration } = node
15
+
16
+ if (!declaration) {
17
+ return node
18
+ }
19
+
20
+ return fetchEdgeDeclaration(declaration)
21
+ }
22
+
23
+ module.exports = {
24
+ meta: {
25
+ type: 'suggestion',
26
+ messages: {
27
+ 'require-export': '{{ message }}',
28
+ },
29
+ schema: SCHEMA,
30
+ },
31
+ create(context) {
32
+ const options = context.options[0]
33
+ const filename = context.getFilename()
34
+ const targetPathRegexs = Object.keys(options)
35
+ const targetRequires = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex)))
36
+
37
+ if (targetRequires.length === 0) {
38
+ return {}
39
+ }
40
+
41
+ return {
42
+ Program: (node) => {
43
+ targetRequires.forEach((requireKey) => {
44
+ const option = options[requireKey]
45
+ let existDefault = false
46
+ const exports =
47
+ node.body
48
+ .filter((i) => {
49
+ if (i.type == 'ExportDefaultDeclaration') {
50
+ existDefault = true
51
+
52
+ return false
53
+ }
54
+
55
+ return i.type == 'ExportNamedDeclaration'
56
+ })
57
+ .map((i) => {
58
+ const declaration = fetchEdgeDeclaration(i)
59
+
60
+ if (declaration.id) {
61
+ return declaration.id.name
62
+ }
63
+ if (declaration.specifiers) {
64
+ return declaration.specifiers.map((s) => s.exported.name)
65
+ }
66
+ if (declaration.declarations) {
67
+ return declaration.declarations.map((d) => d.id.name || d.id.properties.map((p) => p.key.name))
68
+ }
69
+
70
+ return declaration
71
+ })
72
+ .flat(2)
73
+
74
+ let notExistsExports = [...(!existDefault && option.includes('default') ? ['default'] : []), ...option.filter((o) => o !== 'default' && !exports.includes(o))]
75
+
76
+ if (notExistsExports.length) {
77
+ context.report({
78
+ node,
79
+ messageId: 'require-export',
80
+ data: {
81
+ message: `${notExistsExports.join(', ')} をexportしてください`,
82
+ },
83
+ })
84
+ }
85
+ })
86
+ },
87
+ }
88
+ },
89
+ }
90
+ module.exports.schema = SCHEMA
@@ -0,0 +1,51 @@
1
+ # smarthr/require-import
2
+
3
+ - 対象ファイルにimportを強制させたい場合に利用します
4
+ - 例: Page.tsx ではページタイトルを設定させたいので useTitle を必ずimportさせたい
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/require-import': [
12
+ 'error',
13
+ {
14
+ 'Buttons\/.+\.tsx': {
15
+ 'smarthr-ui': {
16
+ imported: ['SecondaryButton'],
17
+ reportMessage: 'Buttons以下のコンポーネントでは {{module}}/{{export}} を拡張するようにしてください',
18
+ },
19
+ },
20
+ 'Page.tsx$': {
21
+ './client/src/hooks/useTitle': {
22
+ imported: true,
23
+ reportMessage: '{{module}} を利用してください(ページタイトルを設定するため必要です)',
24
+ },
25
+ },
26
+ },
27
+ ]
28
+ },
29
+ }
30
+ ```
31
+
32
+ ## ❌ Incorrect
33
+
34
+ ```js
35
+ // client/src/Buttons/SecondaryButton.tsx
36
+ import { SecondaryButtonAnchor } from 'smarthr-ui'
37
+
38
+ // client/src/Page.tsx
39
+ import { SecondaryButton } from 'smarthr-ui'
40
+ ```
41
+
42
+ ## ✅ Correct
43
+
44
+
45
+ ```js
46
+ // client/src/Buttons/SecondaryButton.tsx
47
+ import { SecondaryButton } from 'smarthr-ui'
48
+
49
+ // client/src/Page.tsx
50
+ import useTitle from '.hooks/useTitle'
51
+ ```
@@ -0,0 +1,31 @@
1
+ const rule = require('../rules/best-practice-for-date')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ parserOptions: {
6
+ ecmaVersion: 2018,
7
+ ecmaFeatures: {
8
+ experimentalObjectRestSpread: true,
9
+ jsx: true,
10
+ },
11
+ sourceType: 'module',
12
+ },
13
+ })
14
+
15
+ const errorNewDate = "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。"
16
+ const errorDateParse = 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください'
17
+
18
+ ruleTester.run('best-practice-for-date', rule, {
19
+ valid: [
20
+ { code: `new Date()` },
21
+ { code: `new Date(2022, 11, 31)` },
22
+ { code: `new Date('2022', '11', '31')` },
23
+ { code: `const year = 2022; const month = 11; const date = 31; new Date(year, month, date)` },
24
+ ],
25
+ invalid: [
26
+ { code: 'new Date("2022/12/31")', errors: [ { message: errorNewDate } ] },
27
+ { code: 'const arg = "2022/12/31"; new Date(arg)', errors: [ { message: errorNewDate } ] },
28
+ { code: 'Date.parse("2022/12/31")', errors: [ { message: errorDateParse } ] },
29
+ { code: 'const arg = "2022/12/31"; Date.parse(arg)', errors: [ { message: errorDateParse } ] },
30
+ ]
31
+ })
@@ -0,0 +1,45 @@
1
+ const rule = require('../rules/prohibit-file-name')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ parserOptions: {
6
+ sourceType: 'module',
7
+ ecmaVersion: 2015
8
+ },
9
+ })
10
+
11
+ ruleTester.run('prohibit-file-name', rule, {
12
+ valid: [
13
+ {
14
+ code: 'const any = "code"',
15
+ filename: 'hoge.js',
16
+ options: [
17
+ {
18
+ 'fuga\.js': 'any message.',
19
+ }
20
+ ]
21
+ },
22
+ ],
23
+ invalid: [
24
+ {
25
+ code: 'const any = "code"',
26
+ filename: 'hoge.js',
27
+ options: [
28
+ {
29
+ 'hoge\.js': 'any message.',
30
+ }
31
+ ],
32
+ errors: [{ message: 'any message.' }]
33
+ },
34
+ {
35
+ code: 'const any = "code"',
36
+ filename: 'hoge.js',
37
+ options: [
38
+ {
39
+ '(hoge|fuga)\.js': '$1.jsは作成しないで!',
40
+ }
41
+ ],
42
+ errors: [{ message: 'hoge.jsは作成しないで!' }]
43
+ },
44
+ ]
45
+ })
@@ -0,0 +1,83 @@
1
+ const rule = require('../rules/require-export')
2
+ const RuleTester = require('eslint').RuleTester
3
+
4
+ const ruleTester = new RuleTester({
5
+ parserOptions: {
6
+ sourceType: 'module',
7
+ ecmaVersion: 2015
8
+ },
9
+ })
10
+
11
+ ruleTester.run('require-export', rule, {
12
+ valid: [
13
+ {
14
+ code: `const hoge = {}; export { hoge }`,
15
+ options: [
16
+ {
17
+ '^.+$': ['hoge'],
18
+ }
19
+ ],
20
+ },
21
+ {
22
+ code: `const hoge = {}, fuga = {}; export { hoge, fuga }`,
23
+ options: [
24
+ {
25
+ '^.+$': ['hoge', 'fuga'],
26
+ }
27
+ ],
28
+ },
29
+ {
30
+ code: `const hoge = {}, fuga = {}; export { hoge, fuga }`,
31
+ options: [
32
+ {
33
+ '^.+$': ['fuga'],
34
+ }
35
+ ],
36
+ },
37
+ {
38
+ code: `export const hoge = {}`,
39
+ options: [
40
+ {
41
+ '^.+$': ['hoge'],
42
+ }
43
+ ],
44
+ },
45
+ {
46
+ code: `const hoge = {}; export default hoge`,
47
+ options: [
48
+ {
49
+ '^.+$': ['default'],
50
+ }
51
+ ],
52
+ },
53
+ ],
54
+ invalid: [
55
+ {
56
+ code: `const hoge ={}; export { hoge }`,
57
+ options: [
58
+ {
59
+ '^.+$': ['fuga'],
60
+ }
61
+ ],
62
+ errors: [{ message: 'fuga をexportしてください' }],
63
+ },
64
+ {
65
+ code: `export const hoge = {}`,
66
+ options: [
67
+ {
68
+ '^.+$': ['fuga'],
69
+ }
70
+ ],
71
+ errors: [{ message: 'fuga をexportしてください' }],
72
+ },
73
+ {
74
+ code: `const hoge = {}; export { hoge }`,
75
+ options: [
76
+ {
77
+ '^.+$': ['default'],
78
+ }
79
+ ],
80
+ errors: [{ message: 'default をexportしてください' }],
81
+ },
82
+ ]
83
+ })