eslint-plugin-smarthr 0.1.3 → 0.2.2

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 +21 -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} +10 -2
  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
@@ -1,4 +1,4 @@
1
- const { generateTagFormatter } = require('../libs/format_styled_components')
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
2
2
 
3
3
  const EXPECTED_NAMES = {
4
4
  '(b|B)utton$': 'Button$',
@@ -0,0 +1,55 @@
1
+ # smarthr/a11y-image-has-alt-attribute
2
+
3
+ - 画像やアイコンにalt属性を設定することを強制するルールです
4
+
5
+ ## rules
6
+
7
+ ```js
8
+ {
9
+ rules: {
10
+ 'smarthr/a11y-image-has-alt-attribute': 'error', // 'warn', 'off'
11
+ },
12
+ }
13
+ ```
14
+
15
+ ## ❌ Incorrect
16
+
17
+ ```jsx
18
+ <Img />
19
+ ```
20
+ ```jsx
21
+ <Image>
22
+ <Any />
23
+ </Image>
24
+ ```
25
+ ```jsx
26
+ <Icon />
27
+ ```
28
+ ```jsx
29
+ import styled from 'styled-components'
30
+
31
+ const StyledHoge = styled.img``
32
+ const StyledFuga = styled(Img)``
33
+ const StyledPiyo = styled(Icon)``
34
+ ```
35
+
36
+ ## ✅ Correct
37
+
38
+ ```jsx
39
+ <Img alt="message" />
40
+ ```
41
+ ```jsx
42
+ <Image alt="message">
43
+ <Any />
44
+ </Image>
45
+ ```
46
+ ```jsx
47
+ <Icon alt="message" />
48
+ ```
49
+ ```jsx
50
+ import styled from 'styled-components'
51
+
52
+ const StyledImage = styled.img``
53
+ const StyledImg = styled(Img)``
54
+ const StyledIcon = styled(Icon)``
55
+ ```
@@ -1,4 +1,4 @@
1
- const { generateTagFormatter } = require('../libs/format_styled_components')
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
2
2
 
3
3
  const EXPECTED_NAMES = {
4
4
  'Img$': 'Img$',
@@ -0,0 +1,57 @@
1
+ # smarthr/a11y-trigger-has-button
2
+
3
+ - DropdownTriggerやDialogTriggerの直下にButtonを設置することを強制するルールです
4
+
5
+ ## rules
6
+
7
+ ```js
8
+ {
9
+ rules: {
10
+ 'smarthr/a11y-trigger-has-button': 'error', // 'warn', 'off'
11
+ },
12
+ }
13
+ ```
14
+
15
+ ## ❌ Incorrect
16
+
17
+ ```jsx
18
+ <DropdownTrigger>
19
+ <Xxx />
20
+ </DropdownTrigger>
21
+ ```
22
+ ```jsx
23
+ <DialogTrigger>
24
+ <Yyy />
25
+ </DialogTrigger>
26
+ ```
27
+ ```jsx
28
+ import styled from 'styled-components'
29
+
30
+ const StyledHoge = styled.a``
31
+ const StyledFuga = styled.button``
32
+ const StyledAnchor = styled(Link)``
33
+ const StyledBtn = styled(Button)``
34
+ const StyledPiyo = styled(DropdownTrigger)``
35
+ ```
36
+
37
+ ## ✅ Correct
38
+
39
+ ```jsx
40
+ <DropdownTrigger>
41
+ <Button />
42
+ </DropdownTrigger>
43
+ ```
44
+ ```jsx
45
+ <DialogTrigger>
46
+ <XxxButton />
47
+ </DialogTrigger>
48
+ ```
49
+ ```jsx
50
+ import styled from 'styled-components'
51
+
52
+ const StyledAnchor = styled.a``
53
+ const StyledButton = styled.button``
54
+ const StyledLink = styled(Link)``
55
+ const StyledButton = styled(Button)``
56
+ const StyledDropdownTrigger = styled(DropdownTrigger)``
57
+ ```
@@ -1,4 +1,4 @@
1
- const { generateTagFormatter } = require('../libs/format_styled_components')
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components')
2
2
 
3
3
  const EXPECTED_NAMES = {
4
4
  'DropdownTrigger$': 'DropdownTrigger$',
@@ -0,0 +1,40 @@
1
+ # smarthr/best-practice-for-date
2
+
3
+ - `new Date(arg)` と `Date.parse(arg)` を禁止するルールです
4
+ - `new Date()` と`new Date(year, month, date)` などのように引数が1つ以外の場合は許容します
5
+ - これらはブラウザの実装によっては意図しない日付として解釈されてしまう問題を回避するためのものです
6
+ - [Date オブジェクトを生成するいくつかの方法](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date#date_%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%81%84%E3%81%8F%E3%81%A4%E3%81%8B%E3%81%AE%E6%96%B9%E6%B3%95)
7
+ - [Date.parse](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date#date.parse)
8
+
9
+ ## rules
10
+
11
+ ```js
12
+ {
13
+ rules: {
14
+ 'smarthr/best-practice-for-date': 'error', // 'warn', 'off'
15
+ },
16
+ }
17
+ ```
18
+
19
+ ## ❌ Incorrect
20
+
21
+ ```js
22
+ new Date('2022/12/31')
23
+
24
+ new Date(arg)
25
+
26
+ Date.parse(value)
27
+ ```
28
+
29
+ ## ✅ Correct
30
+
31
+
32
+ ```js
33
+ new Date(2022, 11, 31)
34
+
35
+ const args = arg.split('/')
36
+ new Date(args[0], parseInt(args[1], 10) - 1, args[2])
37
+
38
+ // use dayjs
39
+ dayjs(arg)
40
+ ```
@@ -0,0 +1,42 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ messages: {
5
+ 'best-practice-for-date': '{{ message }}',
6
+ },
7
+ schema: [],
8
+ },
9
+ create(context) {
10
+ return {
11
+ NewExpression: (node) => {
12
+ if (
13
+ node.callee.name === 'Date' &&
14
+ node.arguments.length == 1
15
+ ) {
16
+ context.report({
17
+ node,
18
+ messageId: 'best-practice-for-date',
19
+ data: {
20
+ message: "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。",
21
+ },
22
+ });
23
+ }
24
+ },
25
+ CallExpression: (node) => {
26
+ if (
27
+ node.callee.object?.name === 'Date' &&
28
+ node.callee.property?.name === 'parse'
29
+ ) {
30
+ context.report({
31
+ node,
32
+ messageId: 'best-practice-for-date',
33
+ data: {
34
+ message: 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください',
35
+ },
36
+ });
37
+ }
38
+ },
39
+ }
40
+ },
41
+ }
42
+ module.exports.schema = []
@@ -0,0 +1,99 @@
1
+ # smarthr/format-import-path
2
+
3
+ - importする際のpathをフォーマットするruleです
4
+ - ディレクトリ構造からドメインを識別して相対パス、絶対パスいずれかにするかを判定することが出来ます
5
+ - 例: crews/index 以下同士でのimportは相対パス、crews/index外のファイルimportする場合は絶対パスにする
6
+ - eslint を `--fix` オプション付きで実行すると自動的にパスを置き換えます
7
+
8
+ ## config
9
+
10
+ - tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
11
+ - ドメインを識別するために以下の設定を記述する必要があります
12
+ - globalModuleDir
13
+ - 全体で利用するファイルを収めているディレクトリを相対パスで指定します
14
+ - domainModuleDir:
15
+ - ドメイン内で共通のファイルを収めているディレクトリ名を指定します
16
+ - domainConstituteDir
17
+ - ドメインを構築するディレクトリ名を指定します
18
+
19
+ ### ディレクトリ例
20
+ ```
21
+ / constants
22
+ / modules // 全体共通ディレクトリ
23
+ / crews
24
+ / modules // 共通ディレクトリ
25
+ / views
26
+ / parts
27
+ / index
28
+ / adapters
29
+ / index.ts
30
+ / hoge.ts
31
+ / slices
32
+ / index.ts
33
+ / views
34
+ / index.ts
35
+ / parts
36
+ / Abc.ts
37
+ / show
38
+ / views
39
+ / parts
40
+ ```
41
+
42
+ ### 指定例
43
+ ```
44
+ const DOMAIN_RULE_ARGS = {
45
+ globalModuleDir: [ './constants', './modules' ],
46
+ domainModuleDir: [ 'modules' ],
47
+ domainConstituteDir: [ 'adapters', 'slices', 'views', 'parts' ],
48
+ }
49
+ ```
50
+
51
+ ## rules
52
+
53
+ ```js
54
+ {
55
+ rules: {
56
+ 'smarthr/format-import-path': [
57
+ 'error', // 'warn', 'off'
58
+ {
59
+ ...DOMAIN_RULE_ARGS,
60
+ format: {
61
+ // 'relative', 'auto', 'none'
62
+ // all: 'absolute',
63
+ outside: 'auto',
64
+ globalModule: 'absolute',
65
+ module: 'relative',
66
+ domain: 'relative',
67
+ lower: 'relative',
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ }
73
+ ```
74
+
75
+ ## ❌ Incorrect
76
+
77
+ ```js
78
+ // crews/index/views/index.js
79
+
80
+ import slice from '@/crews/index/slice'
81
+ import hoge from '@/crews/index/adapter/hoge'
82
+ import Abc from '@/crews/index/views/parts/Abc'
83
+ import modulePart from '@/crews/modules/views/part'
84
+ import showPart from '../../show/views/parts'
85
+ import globalModulePart from '../../../module/views/part'
86
+ ```
87
+
88
+ ## ✅ Correct
89
+
90
+ ```js
91
+ // crews/index/views/index.js
92
+
93
+ import slice from '../slice'
94
+ import hoge from '../adapter/hoge'
95
+ import Abc from './parts/Abc'
96
+ import modulePart from '../../modules/views/parts'
97
+ import showPart from '@/crews/show/views/parts'
98
+ import globalModulePart from '@/modules/views/parts'
99
+ ```
@@ -1,6 +1,6 @@
1
1
  const path = require('path')
2
- const { replacePaths } = require('../libs/common')
3
- const { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode } = require('../libs/common_domain')
2
+ const { replacePaths } = require('../../libs/common')
3
+ const { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode } = require('../../libs/common_domain')
4
4
 
5
5
  const SCHEMA_FORMAT_PROPERTY = { type: 'string', pattern: '^(none|absolute|relative|auto)$', default: 'none'}
6
6
  const SCHEMA = [
@@ -0,0 +1,58 @@
1
+ # smarthr/format-translate-component
2
+
3
+ - 翻訳用コンポーネントを適用する際のルールを定めます
4
+
5
+ ## rules
6
+
7
+ ```js
8
+ {
9
+ rules: {
10
+ 'smarthr/format-translate-component': [
11
+ 'error', // 'warn', 'off'
12
+ {
13
+ componentName: 'Translate',
14
+ // componentPath: '@/any/path/Translate',
15
+ // prohibitAttributies: ['data-translate'],
16
+ }
17
+ ]
18
+ },
19
+ }
20
+ ```
21
+
22
+ ## ❌ Incorrect
23
+
24
+ ```jsx
25
+ <Translate><Any>ほげ</Any></Translate>
26
+ ```
27
+
28
+ ```jsx
29
+ <Translate><Any /></Translate>
30
+ ```
31
+
32
+ ```jsx
33
+ <Translate></Translate>
34
+ ```
35
+
36
+ ```jsx
37
+ // prohibitAttributies: ['data-translate'],
38
+ <Any data-translate="true">...</Any>
39
+ ```
40
+
41
+ ## ✅ Correct
42
+
43
+ ```jsx
44
+ <Translate>ほげ</Translate>
45
+ ```
46
+ ```jsx
47
+ <Translate>ほげ<br />ふが</Translate>
48
+ ```
49
+ ```jsx
50
+ <Translate>{any}</Translate>
51
+ ```
52
+ ```jsx
53
+ <Translate dangerouslySetInnerHTML={{ __html: "ほげ" }} />
54
+ ```
55
+ ```jsx
56
+ // prohibitAttributies: ['data-translate'],
57
+ <Any data-hoge="true">...</Any>
58
+ ```
@@ -0,0 +1,31 @@
1
+ # smarthr/jsx-start-with-spread-attributes
2
+
3
+ - jsxを記述する際、意図しない属性の上書きを防ぐため、spread-attributesを先に指定するように強制するruleです
4
+ - eslint を `--fix` オプション付きで実行する際、 fix option を true にすると自動修正します
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/jsx-start-with-spread-attributes': [
12
+ 'error', // 'warn', 'off'
13
+ {
14
+ fix: false, // true
15
+ },
16
+ ]
17
+ },
18
+ }
19
+ ```
20
+
21
+ ## ❌ Incorrect
22
+
23
+ ```jsx
24
+ <AnyComponent hoge="hoge" {...props} />
25
+ ```
26
+
27
+ ## ✅ Correct
28
+
29
+ ```jsx
30
+ <AnyComponent {...props} hoge="hoge" />
31
+ ```
@@ -0,0 +1,85 @@
1
+ # smarthr/no-import-other-domain
2
+
3
+ - ドメイン外からのimportを防ぐruleです
4
+ - 例: crews/index 以下からのimportはOK, crews/index から crews/show 以下をimportするとNG
5
+ - ディレクトリ構造からドメインを識別して判定することが出来ます
6
+
7
+ ## config
8
+
9
+ - tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
10
+ - ドメインを識別するために以下の設定を記述する必要があります
11
+ - globalModuleDir
12
+ - 全体で利用するファイルを収めているディレクトリを相対パスで指定します
13
+ - domainModuleDir:
14
+ - ドメイン内で共通のファイルを収めているディレクトリ名を指定します
15
+ - domainConstituteDir
16
+ - ドメインを構築するディレクトリ名を指定します
17
+
18
+ ### ディレクトリ例
19
+ ```
20
+ / constants
21
+ / modules // 全体共通ディレクトリ
22
+ / crews
23
+ / modules // 共通ディレクトリ
24
+ / views
25
+ / parts
26
+ / index
27
+ / adapters
28
+ / index.ts
29
+ / hoge.ts
30
+ / slices
31
+ / index.ts
32
+ / views
33
+ / index.ts
34
+ / parts
35
+ / Abc.ts
36
+ / show
37
+ / views
38
+ / parts
39
+ ```
40
+
41
+ ### 指定例
42
+ ```
43
+ const DOMAIN_RULE_ARGS = {
44
+ globalModuleDir: [ './constants', './modules' ],
45
+ domainModuleDir: [ 'modules' ],
46
+ domainConstituteDir: [ 'adapters', 'slices', 'views', 'parts' ],
47
+ }
48
+ ```
49
+
50
+ ## rules
51
+
52
+ ```js
53
+ {
54
+ rules: {
55
+ 'smarthr/no-import-other-domain': [
56
+ 'error', // 'warn', 'off'
57
+ {
58
+ ...DOMAIN_RULE_ARGS,
59
+ // analyticsMode: 'all', // 'same-domain', 'another-domain'
60
+ }
61
+ ]
62
+ },
63
+ }
64
+ ```
65
+
66
+ ## ❌ Incorrect
67
+
68
+ ```js
69
+ // crews/index/views/index.js
70
+
71
+ import showPart1 from '@/crews/show/views/parts'
72
+ import showPart2 from '../../show/views/parts'
73
+ ```
74
+
75
+ ## ✅ Correct
76
+
77
+ ```js
78
+ // crews/index/views/index.js
79
+
80
+ import slice from '../slice'
81
+ import hoge from '../adapter/hoge'
82
+ import Abc from './parts/Abc'
83
+ import modulePart from '../../modules/views/parts'
84
+ import globalModulePart from '@/modules/views/parts'
85
+ ```
@@ -1,7 +1,7 @@
1
1
  const path = require('path')
2
2
  const fs = require('fs')
3
- const { replacePaths, rootPath } = require('../libs/common')
4
- const { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode } = require('../libs/common_domain')
3
+ const { replacePaths, rootPath } = require('../../libs/common')
4
+ const { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode } = require('../../libs/common_domain')
5
5
 
6
6
  const SCHEMA = [
7
7
  {
@@ -0,0 +1,28 @@
1
+ # smarthr/prohibit-export-array-type
2
+
3
+ - 配列の型をexport出来ないように制御するルールです
4
+ - 利用するファイルで `ItemProps[]` のように配列指定を強制する目的などで利用できます
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/prohibit-export-array-type': 'error', // 'warn', 'off'
12
+ },
13
+ }
14
+ ```
15
+
16
+ ## ❌ Incorrect
17
+
18
+ ```js
19
+ type Item = { attr: string }
20
+ export type Items = Item[]
21
+ ```
22
+
23
+ ## ✅ Correct
24
+
25
+
26
+ ```js
27
+ export type Item = { attr: string }
28
+ ```
@@ -0,0 +1,28 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ messages: {
5
+ 'prohibit-export-array-type': '{{ message }}',
6
+ },
7
+ schema: [],
8
+ },
9
+ create(context) {
10
+ const checker = (node) => {
11
+ if (node.declaration?.typeAnnotation?.type === 'TSArrayType') {
12
+ context.report({
13
+ node,
14
+ messageId: 'prohibit-export-array-type',
15
+ data: {
16
+ message: '利用する際、配列かどうかわかりにくいため、配列ではない状態でexportしてください',
17
+ },
18
+ })
19
+ }
20
+ }
21
+
22
+ return {
23
+ ExportDefaultDeclaration: checker,
24
+ ExportNamedDeclaration: checker,
25
+ }
26
+ },
27
+ }
28
+ module.exports.schema = []
@@ -0,0 +1,35 @@
1
+ # smarthr/prohibit-file-name
2
+
3
+ - 正規表現に合致するファイル、ディレクトリの作成を阻害するルールです
4
+
5
+ ## rules
6
+
7
+ ```js
8
+ {
9
+ rules: {
10
+ 'smarthr/prohibit-file-name': [
11
+ 'error', // 'warn', 'off'
12
+ {
13
+ '\/(actions|reducers)\/$': 'slicesディレクトリ内でcreateSliceを利用してください',
14
+ '\/views\/(page|template)\.(ts(x)?)$': 'index.$2、もしくはTemplate.$2にリネームしてください',
15
+ '\/modules\/(adapters|entities|repositories|slices)\/index\.ts(x)?$': '利用目的が推測出来ない為、リネームしてください(例: new, edit用ならform.tsなど)',
16
+ },
17
+ ]
18
+ },
19
+ }
20
+ ```
21
+
22
+ ## ❌ Incorrect
23
+
24
+ ```js
25
+ // src/pages/actions/index.ts
26
+ // src/modules/entities/index.ts
27
+ ```
28
+
29
+ ## ✅ Correct
30
+
31
+
32
+ ```js
33
+ // src/pages/slices/index.ts
34
+ // src/modules/entities/item.ts
35
+ ```
@@ -0,0 +1,61 @@
1
+ const SCHEMA = [{
2
+ type: 'object',
3
+ patternProperties: {
4
+ '.+': {
5
+ type: 'string',
6
+ },
7
+ },
8
+ additionalProperties: true,
9
+ }]
10
+
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: 'suggestion',
15
+ messages: {
16
+ 'prohibit-file-name': '{{ message }}',
17
+ },
18
+ schema: SCHEMA,
19
+ },
20
+ create(context) {
21
+ const options = context.options[0]
22
+ const filename = context.getFilename()
23
+ const targetPaths = Object.keys(options).filter((regex) => !!filename.match(new RegExp(regex)))
24
+
25
+
26
+ if (targetPaths.length === 0) {
27
+ return {}
28
+ }
29
+
30
+ const messages = []
31
+
32
+ targetPaths.forEach((path) => {
33
+ const message = options[path]
34
+
35
+ matcher = filename.match(new RegExp(path))
36
+
37
+ if (matcher) {
38
+ messages.push([...matcher].reduce(((prev, k, index) => prev.replaceAll(`\$${index}`, k)), message))
39
+ }
40
+ })
41
+
42
+ if (messages.length === 0) {
43
+ return {}
44
+ }
45
+
46
+ return {
47
+ Program: (node) => {
48
+ messages.forEach((message) => {
49
+ context.report({
50
+ node,
51
+ messageId: 'prohibit-file-name',
52
+ data: {
53
+ message,
54
+ },
55
+ })
56
+ })
57
+ },
58
+ }
59
+ },
60
+ }
61
+ module.exports.schema = SCHEMA