├── .github
└── workflows
│ └── ci-module.yml
├── .gitignore
├── .labrc.cjs
├── API.md
├── LICENSE.md
├── README.md
├── lib
├── configs
│ ├── module.js
│ └── recommended.js
├── index.js
└── rules
│ ├── capitalize-modules.js
│ ├── for-loop.js
│ ├── no-arrowception.js
│ ├── no-var.js
│ └── scope-start.js
├── package.json
└── test
├── configs
├── common.js
├── fixtures
│ ├── arrow-parens.js
│ ├── arrow-spacing.js
│ ├── brace-style.js
│ ├── camelcase.js
│ ├── es6-env.js
│ ├── handle-callback-err.js
│ ├── hapi-capitalize-modules.js
│ ├── hapi-for-you.js
│ ├── hapi-scope-start.js
│ ├── indent-switch-case.js
│ ├── indent.js
│ ├── key-spacing.js
│ ├── no-arrowception.js
│ ├── no-constant-condition.js
│ ├── no-dupe-keys.js
│ ├── no-extra-semi.js
│ ├── no-shadow.js
│ ├── no-undef.js
│ ├── no-unsafe-finally.js
│ ├── no-unused-vars.js
│ ├── no-useless-computed-key.js
│ ├── no-var.js
│ ├── node-env.js
│ ├── object-shorthand.js
│ ├── one-var.js
│ ├── prefer-arrow-callback.js
│ ├── prefer-const.js
│ ├── private-class-field.js
│ ├── semi.js
│ ├── space-before-blocks.js
│ ├── space-before-function-paren.js
│ └── strict.js
├── module.js
└── recommended.js
├── index.js
└── rules
├── capitalize-modules.js
├── for-loop.js
├── no-arrowception.js
├── no-var.js
└── scope-start.js
/.github/workflows/ci-module.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - next
8 | pull_request:
9 | workflow_dispatch:
10 |
11 | jobs:
12 | test:
13 | uses: hapijs/.github/.github/workflows/ci-module.yml@min-node-18-hapi-21
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/package-lock.json
3 |
4 | coverage.*
5 |
6 | **/.DS_Store
7 | **/._*
8 |
9 | **/*.pem
10 |
11 | **/.vs
12 | **/.vscode
13 | **/.idea
14 |
--------------------------------------------------------------------------------
/.labrc.cjs:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | 'lint-options': JSON.stringify({
5 | ignores: ['test/**/fixtures']
6 | })
7 | };
8 |
--------------------------------------------------------------------------------
/API.md:
--------------------------------------------------------------------------------
1 | ## Configurations
2 |
3 | This ESLint plugin exposes two configurations:
4 |
5 | * `@hapi/recommended`
6 | * `@hapi/module`
7 |
8 | ### `@hapi/recommended`
9 |
10 | An ESLint configuration containing hapi style guide rules and config. To use in your project, add
11 | [`@hapi/eslint-plugin`](https://github.com/hapijs/eslint-plugin) to your `package.json`, then in your ESLint configuration add:
12 |
13 | ```json
14 | {
15 | "extends": "plugin:@hapi/recommended"
16 | }
17 | ```
18 |
19 | ### `@hapi/module`
20 |
21 | The ESLint configuration used by [`@hapi/lab`](https://hapi.dev/module/lab/) when you use the linting feature through either the [`-L`](https://hapi.dev/module/lab/api/#command-line) command argument or the `lint: true` configuration.
22 |
23 | To use it in your project, there are multiple dependencies you need to add to your `package.json`:
24 |
25 | * [`@hapi/eslint-plugin`](https://github.com/hapijs/eslint-plugin)
26 | * [`@babel/core`](https://www.npmjs.com/package/@babel/core)
27 | * [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser)
28 |
29 | Then in your ESLint configuration add:
30 |
31 | ```json
32 | {
33 | "extends": "plugin:@hapi/module"
34 | }
35 | ```
36 |
37 | ## Rules
38 |
39 | This plugin includes the following ESLint rules:
40 |
41 | ### capitalize-modules
42 |
43 | Enforces capitalization of imported module variables.
44 |
45 | #### `global-scope-only`
46 |
47 | If the string `'global-scope-only'` is passed as the first option to this rule,
48 | then it will only be enforced on assignments in the module's top level scope.
49 |
50 | ### for-loop
51 |
52 | Enforces `for` loop iterator variable rules and restricts loop nesting depth.
53 |
54 | This rule enforces the following:
55 |
56 | - Restrict iterator variable names. `for` loop iterator variables should be named `i`. Nested loops should use the variables `j`, `k`, and so on.
57 | - Restrict loop nesting. You can restrict the maximum nesting of `for` loops. By default, this limit is three.
58 | - Prevent postfix increment and decrement operators. The hapi style guide does not allow postfix increment and decrement operators in `for` loop updates. The prefix version of these operators should be used instead.
59 | - Single variable declaration in initialization section. A single `var i = 0;` is allowed in the initialization section. This only applies to variable declarations, not assignments to existing variables. This means that `for (i = 0, j = 0)` is allowed if `i` and `j` are existing variables. Variable declarations involving destructuring are not allowed.
60 |
61 | #### Rule options
62 |
63 | This rule can be configured by providing a single options object. The object supports the following keys.
64 |
65 | ##### `maxDepth`
66 |
67 | A number representing the maximum allowed nesting of `for` loops. Defaults to three.
68 |
69 | ##### `startIterator`
70 |
71 | The first variable iterator name to use. This defaults to `'i'`.
72 |
73 | ### no-var
74 |
75 | Enforces the usage of var declarations only in try-catch scope.
76 |
77 | ### scope-start
78 |
79 | Enforces a new line at the beginning of function scope.
80 |
81 | #### `allow-one-liners`
82 |
83 | If the string `'allow-one-liners'` is passed as the first option to this rule,
84 | then functions whose bodies contain zero or one statements are allowed to be
85 | written on a single line. This defaults to `true` for arrow functions, and
86 | `false` otherwise.
87 |
88 | #### `max-in-one-liner`
89 |
90 | The second option to this rule dictates the maximum number of statements allowed
91 | in the bodies of one line function. This must be used in conjunction with
92 | `allow-one-liners`. Defaults to one.
93 |
94 | ### no-arrowception
95 |
96 | Prevents arrow functions that implicitly create additional arrow functions.
97 |
98 | This rule prevents the pattern () => () => () => ...;.
99 |
100 | Functions can still be returned by arrow functions whose bodies use curly braces and an explicit return.
101 |
102 | This rule does not accept any configuration options.
103 |
104 | ## Config
105 |
106 | | Rule | Option |
107 | |------|--------|
108 | | **'@hapi/capitalize-modules'** | ['warn', 'global-scope-only'] |
109 | | **'@hapi/for-loop'** | ['warn', { maxDepth: 3, startIterator: 'i' }] |
110 | | **'@hapi/no-var'** | 'error' |
111 | | **'@hapi/scope-start'** | 'warn' |
112 | | **'@hapi/no-arrowception'** | 'error' |
113 | | **'camelcase'** | 'off' |
114 | | **'consistent-return'** | 'off' |
115 | | **'vars-on-top'** | 'off' |
116 | | **'new-cap'** | 'off |
117 | | **'no-console'** | 'off' |
118 | | **'no-constant-condition'** | 'error' |
119 | | **'no-empty'** | 'off' |
120 | | **'no-native-reassign'** | 'off' |
121 | | **'no-underscore-dangle'** | 'off' |
122 | | **'no-undef'** | ['error', { typeof: false }] |
123 | | **'no-process-exit'** | 'off' |
124 | | **'no-unused-expressions'** | 'off' |
125 | | **'no-regex-spaces'** | 'off' |
126 | | **'no-catch-shadow'** | 'off' |
127 | | **'no-lonely-if'** | 'off' |
128 | | **'brace-style'** | ['warn', 'stroustrup'] |
129 | | **'no-shadow'** | ['warn', { allow: ['err', 'done'] }] |
130 | | **'no-unused-vars'** | ['warn', { vars: 'all', varsIgnorePattern: '^internals$', args: 'none' }] |
131 | | **'one-var'** | ['error', 'never'] |
132 | | **'handle-callback-err'** | ['error', '^(e\|err\|error)$'] |
133 | | **'array-bracket-spacing'** | 'warn' |
134 | | **'dot-notation'** | 'warn' |
135 | | **'eol-last'** | 'warn' |
136 | | **'no-trailing-spaces'** | 'warn' |
137 | | **'no-eq-null'** | 'warn' |
138 | | **'no-extend-native'** | 'warn' |
139 | | **'no-redeclare'** | 'warn' |
140 | | **'no-loop-func'** | 'warn' |
141 | | **'yoda'** | ['warn', 'never'] |
142 | | **'sort-vars'** | 'warn' |
143 | | **'arrow-parens'** | ['error', 'always'] |
144 | | **'arrow-spacing'** | ['error', { before: true, after: true }] |
145 | | **'quotes'** | ['error', 'single', { allowTemplateLiterals: true }] |
146 | | **'consistent-this'** | ['error', 'self'] |
147 | | **'new-parens'** | 'error' |
148 | | **'no-array-constructor'** | 'error' |
149 | | **'no-confusing-arrow'** | 'error' |
150 | | **'no-new-object'** | 'error' |
151 | | **'no-spaced-func'** | 'error' |
152 | | **'no-mixed-spaces-and-tabs'** | 'error' |
153 | | **'key-spacing'** | 'error' |
154 | | **'keyword-spacing'** | ['error', { before: true, after: true }] |
155 | | **'semi'** | ['error', 'always'] |
156 | | **'semi-spacing'** | ['error', { before: false, after: true }] |
157 | | **'space-before-blocks'** | 'error' |
158 | | **'space-infix-ops'** | 'error' |
159 | | **'space-unary-ops'** | ['warn', { words: true, nonwords: false }] |
160 | | **'strict'** | ['error', 'global'] |
161 | | **'eqeqeq'** | 'error' |
162 | | **'curly'** | ['error', 'all'] |
163 | | **'no-eval'** | 'error' |
164 | | **'no-else-return'** | 'error' |
165 | | **'no-return-assign'** | 'error' |
166 | | **'no-new-wrappers'** | 'error' |
167 | | **'comma-dangle'** | ['error', 'never'] |
168 | | **'no-sparse-arrays'** | 'error' |
169 | | **'no-ex-assign'** | 'error' |
170 | | **'prefer-arrow-callback'** | 'error' |
171 | | **'prefer-const'** | ['error', { destructuring: 'all' }] |
172 | | **'indent'** | ['error', 4, { SwitchCase: 1 }] |
173 | | **'space-before-function-paren'** | ['error', { anonymous: 'always', named: 'never' }] |
174 | | **'func-style'** | ['error', 'expression'] |
175 | | **'object-curly-spacing'** | ['error', 'always'] |
176 | | **'object-shorthand'** | ['error', 'properties'] |
177 | | **'no-unsafe-finally'** | 'error' |
178 | | **'no-useless-computed-key'** | 'error' |
179 | | **'require-await'** | 'error' |
180 | | **'constructor-super'** | 'error' |
181 | | **'no-buffer-constructor'** | 'error' |
182 | | **'no-mixed-requires'** | 'error' |
183 | | **'no-new-require'** | 'error' |
184 | | **'no-caller'** | 'error' |
185 | | **'no-const-assign'** | 'error' |
186 | | **'no-dupe-class-members'** | 'error' |
187 | | **'no-class-assign'** | 'warn' |
188 | | **'no-new-symbol'** | 'error |
189 | | **'no-this-before-super'** | 'error' |
190 | | **'prefer-rest-params'** | 'error' |
191 | | **'prefer-spread'** | 'error' |
192 | | **'no-useless-call'** | 'error' |
193 | | **'rest-spread-spacing'** | ['error', 'never'] |
194 | | **'no-extra-semi'** | 'error' |
195 | | **'no-dupe-keys'** | 'error' |
196 | | **'padding-line-between-statements'** | [
'error',
{ blankLine: 'always', prev: 'directive', next: '\*' },
{ blankLine: 'any', prev: 'directive', next: 'directive' },
{ blankLine: 'always', prev: 'cjs-import', next: '\*' },
{ blankLine: 'any', prev: 'cjs-import', next: 'cjs-import' },
{ blankLine: 'always', prev: 'cjs-export', next: '\*' },
{ blankLine: 'always', prev: 'multiline-block-like', next: '\*' },
{ blankLine: 'always', prev: 'class', next: '\*' }
] |
197 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019-2020, Sideway Inc, and project contributors
2 | Copyright (c) 2015-2019 Continuation Labs and Colin J. Ihrig
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # @hapi/eslint-plugin
4 |
5 | #### ESLint plugin containing hapi style guide rules and config.
6 |
7 | **eslint-plugin** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together.
8 |
9 | ### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support
10 |
11 | ## Useful resources
12 |
13 | - [Documentation and API](https://hapi.dev/family/eslint-plugin/)
14 | - [Version status](https://hapi.dev/resources/status/#eslint-plugin) (builds, dependencies, node versions, licenses, eol)
15 | - [Changelog](https://hapi.dev/family/eslint-plugin/changelog/)
16 | - [Project policies](https://hapi.dev/policies/)
17 | - [Free and commercial support options](https://hapi.dev/support/)
18 |
--------------------------------------------------------------------------------
/lib/configs/module.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const BabelParser = require('@babel/eslint-parser');
4 |
5 | module.exports = function (plugin) {
6 |
7 | return [
8 | ...plugin.configs.recommended,
9 | {
10 | languageOptions: {
11 | parser: BabelParser,
12 | parserOptions: {
13 | requireConfigFile: false
14 | },
15 | ecmaVersion: 2020,
16 | sourceType: 'script'
17 | }
18 | }
19 | ];
20 | };
21 |
--------------------------------------------------------------------------------
/lib/configs/recommended.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Globals = require('globals');
4 |
5 | module.exports = function (plugin) {
6 |
7 | return [{
8 | plugins: {
9 | '@hapi': plugin
10 | },
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | sourceType: 'script',
14 | globals: {
15 | ...Globals.es2020,
16 | ...Globals.node
17 | }
18 | },
19 | rules: {
20 | '@hapi/capitalize-modules': ['warn', 'global-scope-only'],
21 | '@hapi/for-loop': ['warn', { maxDepth: 3, startIterator: 'i' }],
22 | '@hapi/no-var': 'error',
23 | '@hapi/scope-start': 'warn',
24 | '@hapi/no-arrowception': 'error',
25 | 'camelcase': 'off',
26 | 'consistent-return': 'off',
27 | 'vars-on-top': 'off',
28 | 'new-cap': 'off',
29 | 'no-console': 'off',
30 | 'no-constant-condition': 'error',
31 | 'no-empty': 'off',
32 | 'no-native-reassign': 'off',
33 | 'no-underscore-dangle': 'off',
34 | 'no-undef': ['error', { typeof: false }],
35 | 'no-process-exit': 'off',
36 | 'no-unused-expressions': 'off',
37 | 'no-regex-spaces': 'off',
38 | 'no-catch-shadow': 'off',
39 | 'no-lonely-if': 'off',
40 | 'brace-style': ['warn', 'stroustrup'],
41 | 'no-shadow': ['warn', { allow: ['err', 'done'] }],
42 | 'no-unused-vars': ['warn', { vars: 'all', caughtErrors: 'all', varsIgnorePattern: '^internals$', args: 'none' }],
43 | 'one-var': ['error', 'never'],
44 | 'handle-callback-err': ['error', '^(e|err|error)$'],
45 | 'array-bracket-spacing': 'warn',
46 | 'dot-notation': 'warn',
47 | 'eol-last': 'warn',
48 | 'no-trailing-spaces': 'warn',
49 | 'no-eq-null': 'warn',
50 | 'no-extend-native': 'warn',
51 | 'no-redeclare': 'warn',
52 | 'no-loop-func': 'warn',
53 | 'yoda': ['warn', 'never'],
54 | 'sort-vars': 'warn',
55 | 'arrow-parens': ['error', 'always'],
56 | 'arrow-spacing': ['error', { before: true, after: true }],
57 | 'quotes': ['error', 'single', { allowTemplateLiterals: true }],
58 | 'consistent-this': ['error', 'self'],
59 | 'new-parens': 'error',
60 | 'no-array-constructor': 'error',
61 | 'no-confusing-arrow': 'error',
62 | 'no-new-object': 'error',
63 | 'no-spaced-func': 'error',
64 | 'no-mixed-spaces-and-tabs': 'error',
65 | 'key-spacing': 'error',
66 | 'keyword-spacing': ['error', { before: true, after: true }],
67 | 'semi': ['error', 'always'],
68 | 'semi-spacing': ['error', { before: false, after: true }],
69 | 'space-before-blocks': 'error',
70 | 'space-infix-ops': 'error',
71 | 'space-unary-ops': ['warn', { words: true, nonwords: false }],
72 | 'strict': ['error', 'global'],
73 | 'eqeqeq': 'error',
74 | 'curly': ['error', 'all'],
75 | 'no-eval': 'error',
76 | 'no-else-return': 'error',
77 | 'no-return-assign': 'error',
78 | 'no-new-wrappers': 'error',
79 | 'comma-dangle': ['error', 'never'],
80 | 'no-sparse-arrays': 'error',
81 | 'no-ex-assign': 'error',
82 | 'prefer-arrow-callback': 'error',
83 | 'prefer-const': ['error', { destructuring: 'all' }],
84 | 'indent': ['error', 4, { SwitchCase: 1 }],
85 | 'space-before-function-paren': ['error', { anonymous: 'always', named: 'never' }],
86 | 'func-style': ['error', 'expression'],
87 | 'object-curly-spacing': ['error', 'always'],
88 | 'object-shorthand': ['error', 'properties'],
89 | 'no-unsafe-finally': 'error',
90 | 'no-useless-computed-key': 'error',
91 | 'require-await': 'error',
92 | 'constructor-super': 'error',
93 | 'no-buffer-constructor': 'error',
94 | 'no-mixed-requires': 'error',
95 | 'no-new-require': 'error',
96 | 'no-caller': 'error',
97 | 'no-const-assign': 'error',
98 | 'no-dupe-class-members': 'error',
99 | 'no-class-assign': 'warn',
100 | 'no-new-symbol': 'error',
101 | 'no-this-before-super': 'error',
102 | 'prefer-rest-params': 'error',
103 | 'prefer-spread': 'error',
104 | 'no-useless-call': 'error',
105 | 'rest-spread-spacing': ['error', 'never'],
106 | 'no-extra-semi': 'error',
107 | 'no-dupe-keys': 'error',
108 | 'padding-line-between-statements': [
109 | 'error',
110 | { blankLine: 'always', prev: 'directive', next: '*' },
111 | { blankLine: 'any', prev: 'directive', next: 'directive' },
112 | { blankLine: 'always', prev: 'cjs-import', next: '*' },
113 | { blankLine: 'any', prev: 'cjs-import', next: 'cjs-import' },
114 | { blankLine: 'always', prev: 'cjs-export', next: '*' },
115 | { blankLine: 'always', prev: 'multiline-block-like', next: '*' },
116 | { blankLine: 'always', prev: 'class', next: '*' }
117 | ]
118 | }
119 | }];
120 | };
121 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const CapitalizeModules = require('./rules/capitalize-modules');
4 | const ForLoop = require('./rules/for-loop');
5 | const NoArrowception = require('./rules/no-arrowception');
6 | const NoVar = require('./rules/no-var');
7 | const Recommended = require('./configs/recommended');
8 | const Module = require('./configs/module');
9 | const ScopeStart = require('./rules/scope-start');
10 |
11 |
12 | const internals = {
13 | plugin: {
14 | configs: {},
15 | rules: {
16 | 'capitalize-modules': CapitalizeModules,
17 | 'for-loop': ForLoop,
18 | 'no-var': NoVar,
19 | 'scope-start': ScopeStart,
20 | 'no-arrowception': NoArrowception
21 | }
22 | }
23 | };
24 |
25 | internals.plugin.configs.recommended = Recommended(internals.plugin);
26 | internals.plugin.configs.module = Module(internals.plugin);
27 |
28 | module.exports = internals.plugin;
29 |
--------------------------------------------------------------------------------
/lib/rules/capitalize-modules.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const internals = {
4 | validTopLevelParents: new Set([
5 | 'AssignmentExpression',
6 | 'VariableDeclarator',
7 | 'MemberExpression',
8 | 'ExpressionStatement',
9 | 'CallExpression',
10 | 'ConditionalExpression',
11 | 'Program',
12 | 'VariableDeclaration'
13 | ])
14 | };
15 |
16 |
17 | module.exports = {
18 | meta: {
19 | type: 'suggestion',
20 | docs: {
21 | description: 'enforce the capitalization of imported module variables',
22 | category: 'Stylistic Issues',
23 | recommended: true
24 | },
25 | schema: [{
26 | enum: ['global-scope-only']
27 | }],
28 | messages: {
29 | notCapitalized: 'Imported module variable name not capitalized.'
30 | }
31 | },
32 | create(context) {
33 |
34 | const sourceCode = context.sourceCode ?? context.getSourceCode();
35 |
36 | const globalScopeOnly = context.options[0] === 'global-scope-only';
37 |
38 | const isCapitalized = function (name) {
39 |
40 | const firstChar = name.charAt(0);
41 | return firstChar === firstChar.toUpperCase();
42 | };
43 |
44 | const isRequire = function (node) {
45 |
46 | return node !== null &&
47 | node.type === 'CallExpression' &&
48 | node.callee.type === 'Identifier' &&
49 | node.callee.name === 'require';
50 | };
51 |
52 | const atTopLevel = function (node) {
53 |
54 | return (sourceCode.getAncestors ? sourceCode.getAncestors(node) : context.getAncestors()).every((parent) => internals.validTopLevelParents.has(parent.type));
55 | };
56 |
57 | const check = function (node) {
58 |
59 | if (globalScopeOnly === true &&
60 | atTopLevel(node) === false) {
61 |
62 | return;
63 | }
64 |
65 | if (node.type === 'VariableDeclarator' &&
66 | node.id.type === 'Identifier' &&
67 | isRequire(node.init) &&
68 | !isCapitalized(node.id.name)) {
69 |
70 | context.report({ node, messageId: 'notCapitalized' });
71 | }
72 | else if (node.type === 'AssignmentExpression' &&
73 | isRequire(node.right) &&
74 | node.left.type === 'Identifier' &&
75 | !isCapitalized(node.left.name)) {
76 |
77 | context.report({ node, messageId: 'notCapitalized' });
78 | }
79 | };
80 |
81 | return {
82 | AssignmentExpression: check,
83 | VariableDeclarator: check
84 | };
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/lib/rules/for-loop.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const internals = {};
4 |
5 |
6 | module.exports = {
7 | meta: {
8 | type: 'suggestion',
9 | docs: {
10 | description: 'enforce for loop syntax',
11 | category: 'Stylistic Issues',
12 | recommended: true
13 | },
14 | schema: [{
15 | type: 'object',
16 | properties: {
17 | maxDepth: {
18 | type: 'integer'
19 | },
20 | startIterator: {
21 | type: 'string'
22 | }
23 | },
24 | additionalProperties: false
25 | }],
26 | messages: {
27 | depthExceeded: 'Too many nested for loops.',
28 | singleInit: 'Only one variable can be initialized per loop.',
29 | singleVar: 'Left hand side of initializer must be a single variable.',
30 | badIter: 'Expected iterator \'{{designatedIter}}\', but got \'{{iteratorVar}}\'.',
31 | usePrefixOp: 'Update to iterator should use prefix operator.'
32 | }
33 | },
34 | create(context) {
35 |
36 | const options = context.options[0] || {};
37 | const maxDepth = options.maxDepth || 3;
38 | const startIterator = options.startIterator || 'i';
39 | const stack = [];
40 |
41 | const getIteratorVariable = function (offset) {
42 |
43 | return String.fromCharCode(startIterator.charCodeAt(0) + offset);
44 | };
45 |
46 | const check = function (node) {
47 |
48 | stack.push(node);
49 |
50 | // Make sure that for loops are not nested excessively
51 |
52 | if (stack.length > maxDepth) {
53 | context.report({ node, messageId: 'depthExceeded' });
54 | }
55 |
56 | const init = node.init;
57 | if (init !== null &&
58 | init.type === 'VariableDeclaration') {
59 |
60 | // Verify that there is 1 initialized variable at most
61 |
62 | if (init.declarations.length > 1) {
63 | context.report({ node, messageId: 'singleInit' });
64 | }
65 |
66 | const declaration = init.declarations[0];
67 |
68 | // Verify that this is a normal variable declaration, not destructuring
69 |
70 | if (declaration.id.type !== 'Identifier') {
71 | context.report({ node, messageId: 'singleVar' });
72 | }
73 | else {
74 | const iteratorVar = declaration.id.name;
75 | const designatedIter = getIteratorVariable(stack.length - 1);
76 |
77 | // Verify that the iterator variable has the expected value
78 |
79 | if (iteratorVar !== designatedIter) {
80 | context.report({ node, messageId: 'badIter', data: { designatedIter, iteratorVar } });
81 | }
82 | }
83 | }
84 |
85 | const update = node.update;
86 |
87 | // Verify that postfix increment/decrement are not used
88 |
89 | if (update && update.type === 'UpdateExpression' &&
90 | !update.prefix) {
91 |
92 | context.report({ node, messageId: 'usePrefixOp' });
93 | }
94 | };
95 |
96 | const popStack = function () {
97 |
98 | stack.pop();
99 | };
100 |
101 | return {
102 | ForStatement: check,
103 | 'ForStatement:exit': popStack
104 | };
105 | }
106 | };
107 |
--------------------------------------------------------------------------------
/lib/rules/no-arrowception.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const internals = {};
4 |
5 |
6 | module.exports = {
7 | meta: {
8 | type: 'problem',
9 | docs: {
10 | description: 'prevent arrow functions that implicitly create arrow functions',
11 | category: 'ECMAScript 6',
12 | recommended: true
13 | },
14 | schema: [],
15 | messages: {
16 | implicitCreate: 'Arrow function implicitly creates arrow function.'
17 | }
18 | },
19 | create(context) {
20 |
21 | const check = function (node) {
22 |
23 | const fnBody = node.body;
24 |
25 | if (fnBody.type === 'ArrowFunctionExpression') {
26 | context.report({ node, messageId: 'implicitCreate' });
27 | }
28 | };
29 |
30 | return {
31 | ArrowFunctionExpression: check
32 | };
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/lib/rules/no-var.js:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/eslint/eslint/blob/master/lib/rules/no-var.js
2 |
3 | 'use strict';
4 |
5 | const internals = {
6 | scopeTypes: new Set(['Program', 'BlockStatement', 'SwitchStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement'])
7 | };
8 |
9 |
10 | module.exports = {
11 | meta: {
12 | type: 'suggestion',
13 | docs: {
14 | description: 'require `let` or `const` instead of `var` when outside of try...catch',
15 | category: 'ECMAScript 6',
16 | recommended: true
17 | },
18 | schema: [],
19 | messages: {
20 | unexpectedVar: 'Unexpected var, use let or const instead.'
21 | }
22 | },
23 | create(context) {
24 |
25 | const sourceCode = context.sourceCode ?? context.getSourceCode();
26 |
27 | const check = (node) => {
28 |
29 | if (node.parent.parent &&
30 | (node.parent.parent.type === 'TryStatement' ||
31 | node.parent.parent.type === 'CatchClause')) {
32 |
33 | const variables = sourceCode.getDeclaredVariables ? sourceCode.getDeclaredVariables(node, context) : context.getDeclaredVariables();
34 | const scopeNode = internals.getScopeNode(node);
35 | if (variables.some(internals.isUsedFromOutsideOf(scopeNode))) {
36 | return;
37 | }
38 | }
39 |
40 | context.report({ node, messageId: 'unexpectedVar' });
41 | };
42 |
43 | return {
44 | 'VariableDeclaration:exit'(node) {
45 |
46 | if (node.kind === 'var') {
47 | check(node);
48 | }
49 | }
50 | };
51 | }
52 | };
53 |
54 |
55 | internals.getScopeNode = function (node) {
56 |
57 | if (internals.scopeTypes.has(node.type)) {
58 | return node;
59 | }
60 |
61 | return internals.getScopeNode(node.parent);
62 | };
63 |
64 |
65 | internals.isUsedFromOutsideOf = function (scopeNode) {
66 |
67 | const isOutsideOfScope = (reference) => {
68 |
69 | const scope = scopeNode.range;
70 | const id = reference.identifier.range;
71 | return id[0] < scope[0] || id[1] > scope[1];
72 | };
73 |
74 | return (variable) => variable.references.some(isOutsideOfScope);
75 | };
76 |
--------------------------------------------------------------------------------
/lib/rules/scope-start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const internals = {};
4 |
5 |
6 | module.exports = {
7 | meta: {
8 | type: 'layout',
9 | docs: {
10 | description: 'enforce new line at the beginning of function scope',
11 | category: 'Stylistic Issues',
12 | recommended: true
13 | },
14 | schema: [{
15 | enum: ['allow-one-liners']
16 | }, {
17 | type: 'integer'
18 | }],
19 | messages: {
20 | missingBlank: 'Missing blank line at beginning of function.'
21 | }
22 | },
23 | create(context) {
24 |
25 | const sourceCode = context.sourceCode ?? context.getSourceCode();
26 |
27 | const maxInOneLiner = context.options[1] !== undefined ? context.options[1] : 1;
28 |
29 | const checkFunction = function (node) {
30 |
31 | const allowOneLiners = context.options[0] === 'allow-one-liners';
32 | check(node, allowOneLiners);
33 | };
34 |
35 | const checkArrow = function (node) {
36 |
37 | check(node, true);
38 | };
39 |
40 | const check = function (node, allowOneLiners) {
41 |
42 | const fnBody = node.body;
43 |
44 | // Arrow functions can return literals that span multiple lines
45 |
46 | if (fnBody.type === 'ObjectExpression' ||
47 | fnBody.type === 'ArrayExpression') {
48 |
49 | return;
50 | }
51 |
52 | const isBlockBody = fnBody.type === 'BlockStatement';
53 | const body = isBlockBody ? fnBody.body : [fnBody];
54 |
55 | // Allow empty function bodies to be of any size
56 |
57 | if (body.length === 0) {
58 | return;
59 | }
60 |
61 | const stmt = body[0];
62 | const bodyStartLine = stmt.loc.start.line;
63 | const openTokenLine = sourceCode.getTokenBefore(stmt).loc.start.line;
64 | const closeTokenLine = isBlockBody ? sourceCode.getTokenAfter(stmt).loc.start.line : sourceCode.getLastToken(stmt).loc.start.line;
65 |
66 | if (allowOneLiners === true &&
67 | body.length <= maxInOneLiner &&
68 | openTokenLine === closeTokenLine) {
69 |
70 | return;
71 | }
72 |
73 | if (bodyStartLine - openTokenLine < 2) {
74 | context.report({ node, messageId: 'missingBlank' });
75 | }
76 | };
77 |
78 | return {
79 | ArrowFunctionExpression: checkArrow,
80 | FunctionExpression: checkFunction,
81 | FunctionDeclaration: checkFunction
82 | };
83 | }
84 | };
85 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hapi/eslint-plugin",
3 | "version": "7.0.0",
4 | "description": "ESLint plugin containing hapi style guide rules and config",
5 | "main": "lib/index.js",
6 | "repository": "https://github.com/hapijs/eslint-plugin.git",
7 | "engines": {
8 | "node": ">=18"
9 | },
10 | "files": [
11 | "lib"
12 | ],
13 | "keywords": [
14 | "lint",
15 | "eslint",
16 | "eslintplugin",
17 | "eslintconfig",
18 | "hapi"
19 | ],
20 | "peerDependencies": {
21 | "@babel/core": "^7.24.9",
22 | "@babel/eslint-parser": "^7.25.0"
23 | },
24 | "peerDependenciesMeta": {
25 | "@babel/core": {
26 | "optional": true
27 | },
28 | "@babel/eslint-parser": {
29 | "optional": true
30 | }
31 | },
32 | "dependencies": {
33 | "globals": "^15.11.0"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.24.9",
37 | "@babel/eslint-parser": "^7.25.0",
38 | "@hapi/code": "^9.0.3",
39 | "@hapi/eslint-plugin": "file:.",
40 | "@hapi/lab": "^25.3.0",
41 | "@types/eslint": "^9.6.0",
42 | "eslint": "^9.8.0"
43 | },
44 | "scripts": {
45 | "test": "lab -a @hapi/code -L"
46 | },
47 | "license": "BSD-3-Clause"
48 | }
49 |
--------------------------------------------------------------------------------
/test/configs/common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function commonTestCases(expect, it, lintFile) {
4 |
5 | it('enforces file level strict mode', async () => {
6 |
7 | const output = await lintFile('fixtures/strict.js');
8 | const results = output[0];
9 |
10 | expect(results.errorCount).to.equal(1);
11 | expect(results.warningCount).to.equal(0);
12 |
13 | const msg = results.messages[0];
14 |
15 | expect(msg.ruleId).to.equal('strict');
16 | expect(msg.severity).to.equal(2);
17 | expect(msg.message).to.equal('Use the global form of \'use strict\'.');
18 | expect(msg.line).to.equal(2);
19 | expect(msg.column).to.equal(1);
20 | expect(msg.nodeType).to.equal('Program');
21 | });
22 |
23 | it('enforces stroustrup style braces', async () => {
24 |
25 | const output = await lintFile('fixtures/brace-style.js');
26 | const results = output[0];
27 |
28 | expect(results.errorCount).to.equal(0);
29 | expect(results.warningCount).to.equal(1);
30 |
31 | const msg = results.messages[0];
32 |
33 | expect(msg.ruleId).to.equal('brace-style');
34 | expect(msg.severity).to.equal(1);
35 | expect(msg.message).to.equal('Closing curly brace appears on the same line as the subsequent block.');
36 | expect(msg.line).to.equal(8);
37 | expect(msg.column).to.equal(1);
38 | expect(msg.nodeType).to.equal('Punctuator');
39 | });
40 |
41 | it('enforces four space indentation', async () => {
42 |
43 | const output = await lintFile('fixtures/indent.js');
44 | const results = output[0];
45 |
46 | expect(results.errorCount).to.equal(1);
47 | expect(results.warningCount).to.equal(0);
48 |
49 | const msg = results.messages[0];
50 |
51 | expect(msg.ruleId).to.equal('indent');
52 | expect(msg.severity).to.equal(2);
53 | expect(msg.message).to.equal('Expected indentation of 4 spaces but found 2.');
54 | expect(msg.line).to.equal(4);
55 | expect(msg.column).to.equal(1);
56 | expect(msg.nodeType).to.equal('Keyword');
57 | });
58 |
59 | it('enforces case indentation in switch statements', async () => {
60 |
61 | const output = await lintFile('fixtures/indent-switch-case.js');
62 | const results = output[0];
63 |
64 | expect(results.errorCount).to.equal(5);
65 | expect(results.warningCount).to.equal(0);
66 |
67 | let msg = results.messages[0];
68 |
69 | expect(msg.ruleId).to.equal('indent');
70 | expect(msg.severity).to.equal(2);
71 | expect(msg.message).to.equal('Expected indentation of 4 spaces but found 0.');
72 | expect(msg.line).to.equal(11);
73 | expect(msg.column).to.equal(1);
74 | expect(msg.nodeType).to.equal('Keyword');
75 |
76 | msg = results.messages[1];
77 |
78 | expect(msg.ruleId).to.equal('indent');
79 | expect(msg.severity).to.equal(2);
80 | expect(msg.message).to.equal('Expected indentation of 8 spaces but found 4.');
81 | expect(msg.line).to.equal(12);
82 | expect(msg.column).to.equal(1);
83 | expect(msg.nodeType).to.equal('Identifier');
84 |
85 | msg = results.messages[2];
86 |
87 | expect(msg.ruleId).to.equal('indent');
88 | expect(msg.severity).to.equal(2);
89 | expect(msg.message).to.equal('Expected indentation of 8 spaces but found 4.');
90 | expect(msg.line).to.equal(13);
91 | expect(msg.column).to.equal(1);
92 | expect(msg.nodeType).to.equal('Keyword');
93 |
94 | msg = results.messages[3];
95 |
96 | expect(msg.ruleId).to.equal('indent');
97 | expect(msg.severity).to.equal(2);
98 | expect(msg.message).to.equal('Expected indentation of 8 spaces but found 4.');
99 | expect(msg.line).to.equal(15);
100 | expect(msg.column).to.equal(1);
101 | expect(msg.nodeType).to.equal('Identifier');
102 |
103 | msg = results.messages[4];
104 |
105 | expect(msg.ruleId).to.equal('indent');
106 | expect(msg.severity).to.equal(2);
107 | expect(msg.message).to.equal('Expected indentation of 8 spaces but found 4.');
108 | expect(msg.line).to.equal(16);
109 | expect(msg.column).to.equal(1);
110 | expect(msg.nodeType).to.equal('Keyword');
111 | });
112 |
113 | it('enforces semicolon usage', async () => {
114 |
115 | const output = await lintFile('fixtures/semi.js');
116 | const results = output[0];
117 |
118 | expect(results.errorCount).to.equal(1);
119 | expect(results.warningCount).to.equal(0);
120 |
121 | const msg = results.messages[0];
122 |
123 | expect(msg.ruleId).to.equal('semi');
124 | expect(msg.severity).to.equal(2);
125 | expect(msg.message).to.equal('Missing semicolon.');
126 | expect(msg.line).to.equal(4);
127 | expect(msg.column).to.equal(14);
128 | expect(msg.nodeType).to.equal('ReturnStatement');
129 | });
130 |
131 | it('enforces no extra semicolons', async () => {
132 |
133 | const output = await lintFile('fixtures/no-extra-semi.js');
134 | const results = output[0];
135 |
136 | expect(results.errorCount).to.equal(1);
137 | expect(results.warningCount).to.equal(0);
138 |
139 | const msg = results.messages[0];
140 |
141 | expect(msg.ruleId).to.equal('no-extra-semi');
142 | expect(msg.severity).to.equal(2);
143 | expect(msg.message).to.equal('Unnecessary semicolon.');
144 | expect(msg.line).to.equal(10);
145 | expect(msg.column).to.equal(6);
146 | expect(msg.nodeType).to.equal('EmptyStatement');
147 | });
148 |
149 | it('enforces space-before-function-paren', async () => {
150 |
151 | const output = await lintFile('fixtures/space-before-function-paren.js');
152 | const results = output[0];
153 |
154 | expect(results.errorCount).to.equal(2);
155 | expect(results.warningCount).to.equal(0);
156 |
157 | let msg = results.messages[0];
158 |
159 | expect(msg.ruleId).to.equal('space-before-function-paren');
160 | expect(msg.severity).to.equal(2);
161 | expect(msg.message).to.equal('Missing space before function parentheses.');
162 | expect(msg.line).to.equal(8);
163 | expect(msg.column).to.equal(21);
164 | expect(msg.nodeType).to.equal('FunctionExpression');
165 |
166 | msg = results.messages[1];
167 |
168 | expect(msg.ruleId).to.equal('space-before-function-paren');
169 | expect(msg.severity).to.equal(2);
170 | expect(msg.message).to.equal('Unexpected space before function parentheses.');
171 | expect(msg.line).to.equal(16);
172 | expect(msg.column).to.equal(27);
173 | expect(msg.nodeType).to.equal('FunctionExpression');
174 | });
175 |
176 | it('enforces @hapi/for-loop', async () => {
177 |
178 | const output = await lintFile('fixtures/hapi-for-you.js');
179 | const results = output[0];
180 |
181 | expect(results.errorCount).to.equal(0);
182 | expect(results.warningCount).to.equal(2);
183 |
184 | let msg = results.messages[0];
185 |
186 | expect(msg.ruleId).to.equal('@hapi/for-loop');
187 | expect(msg.severity).to.equal(1);
188 | expect(msg.message).to.equal('Expected iterator \'j\', but got \'k\'.');
189 | expect(msg.line).to.equal(6);
190 | expect(msg.column).to.equal(5);
191 | expect(msg.nodeType).to.equal('ForStatement');
192 |
193 | msg = results.messages[1];
194 |
195 | expect(msg.ruleId).to.equal('@hapi/for-loop');
196 | expect(msg.severity).to.equal(1);
197 | expect(msg.message).to.equal('Update to iterator should use prefix operator.');
198 | expect(msg.line).to.equal(6);
199 | expect(msg.column).to.equal(5);
200 | expect(msg.nodeType).to.equal('ForStatement');
201 | });
202 |
203 | it('enforces @hapi/scope-start', async () => {
204 |
205 | const output = await lintFile('fixtures/hapi-scope-start.js');
206 | const results = output[0];
207 |
208 | expect(results.errorCount).to.equal(0);
209 | expect(results.warningCount).to.equal(1);
210 |
211 | const msg = results.messages[0];
212 |
213 | expect(msg.ruleId).to.equal('@hapi/scope-start');
214 | expect(msg.severity).to.equal(1);
215 | expect(msg.message).to.equal('Missing blank line at beginning of function.');
216 | expect(msg.line).to.equal(2);
217 | expect(msg.column).to.equal(13);
218 | expect(msg.nodeType).to.equal('FunctionExpression');
219 | });
220 |
221 | it('enforces @hapi/capitalize-modules', async () => {
222 |
223 | const output = await lintFile('fixtures/hapi-capitalize-modules.js');
224 | const results = output[0];
225 |
226 | expect(results.errorCount).to.equal(0);
227 | expect(results.warningCount).to.equal(1);
228 |
229 | const msg = results.messages[0];
230 |
231 | expect(msg.ruleId).to.equal('@hapi/capitalize-modules');
232 | expect(msg.severity).to.equal(1);
233 | expect(msg.message).to.equal('Imported module variable name not capitalized.');
234 | expect(msg.line).to.equal(5);
235 | expect(msg.column).to.equal(7);
236 | expect(msg.nodeType).to.equal('VariableDeclarator');
237 | });
238 |
239 | it('enforces @hapi/no-arrowception', async () => {
240 |
241 | const output = await lintFile('fixtures/no-arrowception.js');
242 | const results = output[0];
243 |
244 | expect(results.errorCount).to.equal(1);
245 | expect(results.warningCount).to.equal(0);
246 |
247 | const msg = results.messages[0];
248 |
249 | expect(msg.ruleId).to.equal('@hapi/no-arrowception');
250 | expect(msg.severity).to.equal(2);
251 | expect(msg.message).to.equal('Arrow function implicitly creates arrow function.');
252 | expect(msg.line).to.equal(2);
253 | expect(msg.column).to.equal(13);
254 | expect(msg.nodeType).to.equal('ArrowFunctionExpression');
255 | });
256 |
257 | it('enforces no-shadow rule', async () => {
258 |
259 | const output = await lintFile('fixtures/no-shadow.js');
260 | const results = output[0];
261 |
262 | expect(results.errorCount).to.equal(0);
263 | expect(results.warningCount).to.equal(1);
264 |
265 | const msg = results.messages[0];
266 |
267 | expect(msg.ruleId).to.equal('no-shadow');
268 | expect(msg.severity).to.equal(1);
269 | expect(msg.message).to.equal('\'res\' is already declared in the upper scope on line 25 column 27.');
270 | expect(msg.line).to.equal(27);
271 | expect(msg.column).to.equal(33);
272 | expect(msg.nodeType).to.equal('Identifier');
273 | });
274 |
275 | it('enforces one-var rule', async () => {
276 |
277 | const output = await lintFile('fixtures/one-var.js');
278 | const results = output[0];
279 |
280 | expect(results.errorCount).to.equal(1);
281 | expect(results.warningCount).to.equal(0);
282 |
283 | const msg = results.messages[0];
284 |
285 | expect(msg.ruleId).to.equal('one-var');
286 | expect(msg.severity).to.equal(2);
287 | expect(msg.message).to.equal('Split \'let\' declarations into multiple statements.');
288 | expect(msg.line).to.equal(6);
289 | expect(msg.column).to.equal(1);
290 | expect(msg.nodeType).to.equal('VariableDeclaration');
291 | });
292 |
293 | it('enforces no-undef rule', async () => {
294 |
295 | const output = await lintFile('fixtures/no-undef.js');
296 | const results = output[0];
297 |
298 | expect(results.errorCount).to.equal(1);
299 | expect(results.warningCount).to.equal(0);
300 |
301 | const msg = results.messages[0];
302 |
303 | expect(msg.ruleId).to.equal('no-undef');
304 | expect(msg.severity).to.equal(2);
305 | expect(msg.message).to.equal('\'bar\' is not defined.');
306 | expect(msg.line).to.equal(6);
307 | expect(msg.column).to.equal(17);
308 | expect(msg.nodeType).to.equal('Identifier');
309 | });
310 |
311 | it('enforces no-unused-vars', async () => {
312 |
313 | const output = await lintFile('fixtures/no-unused-vars.js');
314 | const results = output[0];
315 |
316 | expect(results.errorCount).to.equal(0);
317 | expect(results.warningCount).to.equal(1);
318 |
319 | const msg = results.messages[0];
320 |
321 | expect(msg.ruleId).to.equal('no-unused-vars');
322 | expect(msg.severity).to.equal(1);
323 | expect(msg.message).to.match(/'internals2' is assigned a value but never used\./);
324 | expect(msg.line).to.equal(3);
325 | expect(msg.column).to.equal(7);
326 | expect(msg.nodeType).to.equal('Identifier');
327 | });
328 |
329 | it('enforces prefer-const', async () => {
330 |
331 | const output = await lintFile('fixtures/prefer-const.js');
332 | const results = output[0];
333 |
334 | expect(results.errorCount).to.equal(1);
335 | expect(results.warningCount).to.equal(0);
336 |
337 | const msg = results.messages[0];
338 |
339 | expect(msg.ruleId).to.equal('prefer-const');
340 | expect(msg.severity).to.equal(2);
341 | expect(msg.message).to.equal('\'foo\' is never reassigned. Use \'const\' instead.');
342 | expect(msg.line).to.equal(4);
343 | expect(msg.column).to.equal(5);
344 | expect(msg.nodeType).to.equal('Identifier');
345 | });
346 |
347 | it('enforces @hapi/no-var', async () => {
348 |
349 | const output = await lintFile('fixtures/no-var.js');
350 | const results = output[0];
351 |
352 | expect(results.errorCount).to.equal(1);
353 | expect(results.warningCount).to.equal(0);
354 |
355 | const msg = results.messages[0];
356 |
357 | expect(msg.ruleId).to.equal('@hapi/no-var');
358 | expect(msg.severity).to.equal(2);
359 | expect(msg.message).to.equal('Unexpected var, use let or const instead.');
360 | expect(msg.line).to.equal(4);
361 | expect(msg.column).to.equal(1);
362 | expect(msg.nodeType).to.equal('VariableDeclaration');
363 | });
364 |
365 | it('enforces arrow-parens', async () => {
366 |
367 | const output = await lintFile('fixtures/arrow-parens.js');
368 | const results = output[0];
369 |
370 | expect(results.errorCount).to.equal(1);
371 | expect(results.warningCount).to.equal(0);
372 |
373 | const msg = results.messages[0];
374 |
375 | expect(msg.ruleId).to.equal('arrow-parens');
376 | expect(msg.severity).to.equal(2);
377 | expect(msg.message).to.equal('Expected parentheses around arrow function argument.');
378 | expect(msg.line).to.equal(2);
379 | expect(msg.column).to.equal(13);
380 | expect(msg.nodeType).to.equal('ArrowFunctionExpression');
381 | });
382 |
383 | it('enforces arrow-spacing', async () => {
384 |
385 | const output = await lintFile('fixtures/arrow-spacing.js');
386 | const results = output[0];
387 |
388 | expect(results.errorCount).to.equal(2);
389 | expect(results.warningCount).to.equal(0);
390 |
391 | let msg = results.messages[0];
392 | expect(msg.ruleId).to.equal('arrow-spacing');
393 | expect(msg.severity).to.equal(2);
394 | expect(msg.message).to.equal('Missing space before =>.');
395 | expect(msg.line).to.equal(2);
396 | expect(msg.column).to.equal(17);
397 | expect(msg.nodeType).to.equal('Punctuator');
398 |
399 | msg = results.messages[1];
400 | expect(msg.ruleId).to.equal('arrow-spacing');
401 | expect(msg.severity).to.equal(2);
402 | expect(msg.message).to.equal('Missing space after =>.');
403 | expect(msg.line).to.equal(7);
404 | expect(msg.column).to.equal(22);
405 | expect(msg.nodeType).to.equal('Punctuator');
406 | });
407 |
408 | it('enforces object-shorthand', async () => {
409 |
410 | const output = await lintFile('fixtures/object-shorthand.js');
411 | const results = output[0];
412 |
413 | expect(results.errorCount).to.equal(1);
414 | expect(results.warningCount).to.equal(0);
415 |
416 | const msg = results.messages[0];
417 |
418 | expect(msg.ruleId).to.equal('object-shorthand');
419 | expect(msg.severity).to.equal(2);
420 | expect(msg.message).to.equal('Expected property shorthand.');
421 | expect(msg.line).to.equal(10);
422 | expect(msg.column).to.equal(5);
423 | expect(msg.nodeType).to.equal('Property');
424 | });
425 |
426 | it('enforces prefer-arrow-callback', async () => {
427 |
428 | const output = await lintFile('fixtures/prefer-arrow-callback.js');
429 | const results = output[0];
430 |
431 | expect(results.errorCount).to.equal(1);
432 | expect(results.warningCount).to.equal(0);
433 |
434 | const msg = results.messages[0];
435 |
436 | expect(msg.ruleId).to.equal('prefer-arrow-callback');
437 | expect(msg.severity).to.equal(2);
438 | expect(msg.message).to.equal('Unexpected function expression.');
439 | expect(msg.line).to.equal(22);
440 | expect(msg.column).to.equal(8);
441 | expect(msg.nodeType).to.equal('FunctionExpression');
442 | });
443 |
444 | it('enforces no-constant-condition rule', async () => {
445 |
446 | const output = await lintFile('fixtures/no-constant-condition.js');
447 | const results = output[0];
448 |
449 | expect(results.errorCount).to.equal(1);
450 | expect(results.warningCount).to.equal(0);
451 |
452 | const msg = results.messages[0];
453 |
454 | expect(msg.ruleId).to.equal('no-constant-condition');
455 | expect(msg.severity).to.equal(2);
456 | expect(msg.message).to.equal('Unexpected constant condition.');
457 | expect(msg.line).to.equal(3);
458 | expect(msg.column).to.equal(5);
459 | expect(msg.nodeType).to.equal('ArrowFunctionExpression');
460 | });
461 |
462 | it('enforces no-unsafe-finally rule', async () => {
463 |
464 | const output = await lintFile('fixtures/no-unsafe-finally.js');
465 | const results = output[0];
466 |
467 | expect(results.errorCount).to.equal(1);
468 | expect(results.warningCount).to.equal(0);
469 |
470 | const msg = results.messages[0];
471 |
472 | expect(msg.ruleId).to.equal('no-unsafe-finally');
473 | expect(msg.severity).to.equal(2);
474 | expect(msg.message).to.equal('Unsafe usage of ReturnStatement.');
475 | expect(msg.line).to.equal(12);
476 | expect(msg.column).to.equal(9);
477 | expect(msg.nodeType).to.equal('ReturnStatement');
478 | });
479 |
480 | it('enforces no-useless-computed-key rule', async () => {
481 |
482 | const output = await lintFile('fixtures/no-useless-computed-key.js');
483 | const results = output[0];
484 |
485 | expect(results.errorCount).to.equal(5);
486 | expect(results.warningCount).to.equal(0);
487 |
488 | let msg = results.messages[0];
489 |
490 | expect(msg.ruleId).to.equal('no-useless-computed-key');
491 | expect(msg.severity).to.equal(2);
492 | expect(msg.message).to.equal('Unnecessarily computed property [\'0\'] found.');
493 | expect(msg.line).to.equal(2);
494 | expect(msg.column).to.equal(22);
495 | expect(msg.nodeType).to.equal('Property');
496 |
497 | msg = results.messages[1];
498 |
499 | expect(msg.ruleId).to.equal('no-useless-computed-key');
500 | expect(msg.severity).to.equal(2);
501 | expect(msg.message).to.equal('Unnecessarily computed property [\'0+1,234\'] found.');
502 | expect(msg.line).to.equal(3);
503 | expect(msg.column).to.equal(22);
504 | expect(msg.nodeType).to.equal('Property');
505 |
506 | msg = results.messages[2];
507 |
508 | expect(msg.ruleId).to.equal('no-useless-computed-key');
509 | expect(msg.severity).to.equal(2);
510 | expect(msg.message).to.equal('Unnecessarily computed property [0] found.');
511 | expect(msg.line).to.equal(4);
512 | expect(msg.column).to.equal(22);
513 | expect(msg.nodeType).to.equal('Property');
514 |
515 | msg = results.messages[3];
516 |
517 | expect(msg.ruleId).to.equal('no-useless-computed-key');
518 | expect(msg.severity).to.equal(2);
519 | expect(msg.message).to.equal('Unnecessarily computed property [\'x\'] found.');
520 | expect(msg.line).to.equal(5);
521 | expect(msg.column).to.equal(22);
522 | expect(msg.nodeType).to.equal('Property');
523 |
524 | msg = results.messages[4];
525 |
526 | expect(msg.ruleId).to.equal('no-useless-computed-key');
527 | expect(msg.severity).to.equal(2);
528 | expect(msg.message).to.equal('Unnecessarily computed property [\'x\'] found.');
529 | expect(msg.line).to.equal(6);
530 | expect(msg.column).to.equal(22);
531 | expect(msg.nodeType).to.equal('Property');
532 | });
533 |
534 | it('enforces handle-callback-err rule', async () => {
535 |
536 | const output = await lintFile('fixtures/handle-callback-err.js');
537 | const results = output[0];
538 |
539 | expect(results.errorCount).to.equal(2);
540 | expect(results.warningCount).to.equal(0);
541 |
542 | let msg = results.messages[0];
543 |
544 | expect(msg.ruleId).to.equal('handle-callback-err');
545 | expect(msg.severity).to.equal(2);
546 | expect(msg.message).to.equal('Expected error to be handled.');
547 | expect(msg.line).to.equal(6);
548 | expect(msg.column).to.equal(17);
549 | expect(msg.nodeType).to.equal('FunctionExpression');
550 |
551 | msg = results.messages[1];
552 |
553 | expect(msg.ruleId).to.equal('handle-callback-err');
554 | expect(msg.severity).to.equal(2);
555 | expect(msg.message).to.equal('Expected error to be handled.');
556 | expect(msg.line).to.equal(8);
557 | expect(msg.column).to.equal(23);
558 | expect(msg.nodeType).to.equal('FunctionExpression');
559 | });
560 |
561 | it('enforces no-dupe-keys rule', async () => {
562 |
563 | const output = await lintFile('fixtures/no-dupe-keys.js');
564 | const results = output[0];
565 |
566 | expect(results.errorCount).to.equal(1);
567 | expect(results.warningCount).to.equal(0);
568 |
569 | const msg = results.messages[0];
570 |
571 | expect(msg.ruleId).to.equal('no-dupe-keys');
572 | expect(msg.severity).to.equal(2);
573 | expect(msg.message).to.equal('Duplicate key \'a\'.');
574 | expect(msg.line).to.equal(6);
575 | expect(msg.column).to.equal(5);
576 | expect(msg.nodeType).to.equal('ObjectExpression');
577 | });
578 |
579 | it('uses the node environment', async () => {
580 |
581 | const output = await lintFile('fixtures/node-env.js');
582 | const results = output[0];
583 |
584 | expect(results.errorCount).to.equal(0);
585 | expect(results.warningCount).to.equal(0);
586 | expect(results.messages).to.equal([]);
587 | });
588 |
589 | it('uses the ES6 environment', async () => {
590 |
591 | const output = await lintFile('fixtures/es6-env.js');
592 | const results = output[0];
593 |
594 | expect(results.errorCount).to.equal(0);
595 | expect(results.warningCount).to.equal(0);
596 | expect(results.messages).to.equal([]);
597 | });
598 |
599 | it('does not enforce the camelcase lint rule', async () => {
600 |
601 | const output = await lintFile('fixtures/camelcase.js');
602 | const results = output[0];
603 |
604 | expect(results.errorCount).to.equal(0);
605 | expect(results.warningCount).to.equal(0);
606 | expect(results.messages).to.equal([]);
607 | });
608 |
609 | it('enforces key-spacing', async () => {
610 |
611 | const output = await lintFile('fixtures/key-spacing.js');
612 | const results = output[0];
613 |
614 | expect(results.errorCount).to.equal(2);
615 | expect(results.warningCount).to.equal(0);
616 | });
617 |
618 | it('enforces space-before-blocks', async () => {
619 |
620 | const output = await lintFile('fixtures/space-before-blocks.js');
621 | const results = output[0];
622 |
623 | expect(results.errorCount).to.equal(2);
624 | expect(results.warningCount).to.equal(0);
625 | });
626 | };
627 |
--------------------------------------------------------------------------------
/test/configs/fixtures/arrow-parens.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars */
2 | const foo = bar => {
3 |
4 | return bar + 1;
5 | };
6 |
7 | const baz = (quux) => {
8 |
9 | return quux + 1;
10 | };
11 |
--------------------------------------------------------------------------------
/test/configs/fixtures/arrow-spacing.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars */
2 | const foo = (bar)=> {
3 |
4 | return bar + 1;
5 | };
6 |
7 | const baz = (quux) =>{
8 |
9 | return quux + 1;
10 | };
11 |
12 | const fn = (arg) => {
13 |
14 | return arg + 1;
15 | };
16 |
--------------------------------------------------------------------------------
/test/configs/fixtures/brace-style.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const foo = true;
4 | let bar = 0;
5 |
6 | if (foo) {
7 | bar = 1;
8 | } else {
9 | bar = 2;
10 | }
11 |
12 | if (foo) {
13 | bar = 3;
14 | }
15 | else {
16 | bar = 4;
17 | }
18 |
19 | bar.toString();
20 |
--------------------------------------------------------------------------------
/test/configs/fixtures/camelcase.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | const foo_bar = '123';
3 | const barBaz = '456';
4 |
5 | foo_bar + barBaz;
6 |
--------------------------------------------------------------------------------
/test/configs/fixtures/es6-env.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | module.exports = `__filename = ${__filename}`;
3 |
--------------------------------------------------------------------------------
/test/configs/fixtures/handle-callback-err.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | module.exports.foo = function (value) {
5 |
6 | const top = function (err) {
7 |
8 | const inner = function (e) {
9 |
10 | return value;
11 | };
12 | };
13 |
14 | top();
15 | };
16 |
17 |
18 | module.exports.bar = function (value) {
19 |
20 | const top = function (abc) {
21 |
22 | const inner = function (xyz) {
23 |
24 | return value;
25 | };
26 | };
27 |
28 | top();
29 | };
30 |
--------------------------------------------------------------------------------
/test/configs/fixtures/hapi-capitalize-modules.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | const Fs = require('fs');
5 | const net = require('net');
6 |
7 | const fn = function () {
8 |
9 | const Assert = require('assert');
10 | const dgram = require('dgram');
11 | };
12 |
13 | fn();
14 |
--------------------------------------------------------------------------------
/test/configs/fixtures/hapi-for-you.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const arr = [];
4 |
5 | for (let i = 0; i < arr.length; ++i) {
6 | for (let k = 0; k < arr.length; k++) {
7 |
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/configs/fixtures/hapi-scope-start.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars */
2 | const foo = function () {
3 | return 'there should be a blank line before this line';
4 | };
5 |
6 | const bar = function () {
7 |
8 | return 'no lint errors';
9 | };
10 |
11 | const baz = () => {
12 |
13 | return 'no lint errors';
14 | };
15 |
16 | const quux = () => 85; // no lint errors
17 |
18 | const buux = () => ({
19 | a: 'b'
20 | });
21 |
--------------------------------------------------------------------------------
/test/configs/fixtures/indent-switch-case.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const foo = 'foo';
4 | let result = 0;
5 |
6 | switch (foo) {
7 | case 'foo':
8 | result = 1;
9 | break;
10 |
11 | case 'bar':
12 | result = 2;
13 | break;
14 | case 'baz':
15 | result = 3;
16 | break;
17 | }
18 |
19 | result.toString();
20 |
--------------------------------------------------------------------------------
/test/configs/fixtures/indent.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | module.exports.foo = function (value) {
3 |
4 | return value + 1;
5 | };
6 |
7 |
8 | module.exports.foo = function (value) {
9 |
10 | return value + 1;
11 | };
12 |
--------------------------------------------------------------------------------
/test/configs/fixtures/key-spacing.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars */
2 | const a = {
3 | b: 'c',
4 | c : 'd'
5 | };
6 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-arrowception.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars */
2 | const foo = () => () => 85;
3 | const bar = () => 85;
4 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-constant-condition.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if ((foo) => 1) {
4 | // Do nothing
5 | }
6 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-dupe-keys.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | const obj = {
5 | a: 1,
6 | a: 2
7 | };
8 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-extra-semi.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable padding-line-between-statements,strict */
2 |
3 | module.exports.foo = function () {
4 |
5 | try {
6 |
7 | }
8 | catch {
9 |
10 | };
11 |
12 | };
13 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-shadow.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars, handle-callback-err */
2 |
3 |
4 | // Declare internals
5 |
6 | const internals = {};
7 |
8 |
9 | module.exports.foo = function (value) {
10 |
11 | const top = function (err) {
12 |
13 | const inner = function (err) {
14 |
15 | return value;
16 | };
17 | };
18 |
19 | top();
20 | };
21 |
22 |
23 | module.exports.bar = function (value) {
24 |
25 | const top = function (res) {
26 |
27 | const inner = function (res) {
28 |
29 | return value;
30 | };
31 | };
32 |
33 | top();
34 | };
35 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-undef.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | try {
5 | const foo = typeof bar;
6 | const baz = bar;
7 | }
8 | catch (ignoreErr) {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-unsafe-finally.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | module.exports.foo = function () {
3 |
4 | try {
5 | return 1;
6 | }
7 | catch {
8 |
9 | return 2;
10 | }
11 | finally {
12 | return 3;
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-unused-vars.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | const internals = {};
3 | const internals2 = {};
4 | const bar = function (foo) {
5 |
6 | };
7 |
8 | bar.toString();
9 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-useless-computed-key.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, padding-line-between-statements */
2 | module.exports.a = { ['0']: 0 };
3 | module.exports.a = { ['0+1,234']: 0 };
4 | module.exports.a = { [0]: 0 };
5 | module.exports.a = { ['x']: 0 };
6 | module.exports.a = { ['x']() {} };
7 |
--------------------------------------------------------------------------------
/test/configs/fixtures/no-var.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | var foo = 1;
5 | let bar = 2;
6 | const baz = 3;
7 |
8 | bar = 4;
9 |
--------------------------------------------------------------------------------
/test/configs/fixtures/node-env.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | const Fs = require('fs');
3 |
4 | module.exports = Fs;
5 |
--------------------------------------------------------------------------------
/test/configs/fixtures/object-shorthand.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | const a = 1;
5 | const b = 2;
6 | const c = function () {};
7 | const d = function () {};
8 | const obj = {
9 | a,
10 | b: b,
11 | c: function () {},
12 | d() {}
13 | };
14 |
--------------------------------------------------------------------------------
/test/configs/fixtures/one-var.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars, prefer-const */
2 | 'use strict';
3 |
4 | const foo = 1;
5 | let bar;
6 | let baz, quux;
7 |
8 | bar = 1;
9 |
--------------------------------------------------------------------------------
/test/configs/fixtures/prefer-arrow-callback.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable handle-callback-err */
2 | 'use strict';
3 |
4 | const foo = (arg, callback) => {
5 |
6 | return callback(null, arg + 1);
7 | };
8 |
9 | const bar = function (err, value) {
10 |
11 | };
12 |
13 | const baz = (err, value) => {
14 |
15 | };
16 |
17 | foo(1, bar);
18 | foo(2, baz);
19 | foo(3, (err, value) => {
20 |
21 | });
22 | foo(4, function (err, value) {
23 |
24 | });
25 |
--------------------------------------------------------------------------------
/test/configs/fixtures/prefer-const.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | let foo = 1;
5 | let bar = 2;
6 | const baz = 3;
7 |
8 | bar++;
9 |
10 | let { a, b } = { a: 1, b: 2 };
11 | a++;
12 |
--------------------------------------------------------------------------------
/test/configs/fixtures/private-class-field.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-unused-vars */
2 |
3 | class Test {
4 | #a = 1;
5 | }
6 |
--------------------------------------------------------------------------------
/test/configs/fixtures/semi.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 | module.exports.foo = function () {
3 |
4 | return 42
5 | };
6 |
7 | module.exports.bar = function () {
8 |
9 | return 85;
10 | };
11 |
--------------------------------------------------------------------------------
/test/configs/fixtures/space-before-blocks.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | const foo = function (){
5 |
6 | };
7 |
8 | const bar = function () {
9 |
10 | };
11 |
12 | const baz = function (){};
13 |
--------------------------------------------------------------------------------
/test/configs/fixtures/space-before-function-paren.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | 'use strict';
3 |
4 | const foo = function () {
5 |
6 | };
7 |
8 | const bar = function() {
9 |
10 | };
11 |
12 | const baz = function baz() {
13 |
14 | };
15 |
16 | const quux = function quux () {
17 |
18 | };
19 |
--------------------------------------------------------------------------------
/test/configs/fixtures/strict.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | const foo = 'this should be using strict mode but isnt';
3 |
--------------------------------------------------------------------------------
/test/configs/module.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Fs = require('fs');
4 | const Path = require('path');
5 |
6 | const Code = require('@hapi/code');
7 | const ESLint = require('eslint');
8 | const Lab = require('@hapi/lab');
9 |
10 | const CommonTestCases = require('./common');
11 | const HapiPlugin = require('../../lib');
12 |
13 |
14 | const internals = {};
15 |
16 |
17 | const { describe, it } = exports.lab = Lab.script();
18 | const expect = Code.expect;
19 |
20 |
21 | Code.settings.truncateMessages = false;
22 |
23 |
24 | internals.lintFile = async function (file) {
25 |
26 | const cli = new ESLint.ESLint({
27 | overrideConfigFile: true,
28 | baseConfig: [...HapiPlugin.configs.module]
29 | });
30 |
31 | const data = await Fs.promises.readFile(Path.join(__dirname, file), 'utf8');
32 | return await cli.lintText(data);
33 | };
34 |
35 | describe('internal config', () => {
36 |
37 | CommonTestCases(expect, it, internals.lintFile);
38 |
39 | it('parses private class fields', async () => {
40 |
41 | const output = await internals.lintFile('fixtures/private-class-field.js');
42 | const results = output[0];
43 |
44 | expect(results.errorCount).to.equal(0);
45 | expect(results.warningCount).to.equal(0);
46 |
47 | expect(results.messages).to.be.empty();
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/test/configs/recommended.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Fs = require('fs');
4 | const Path = require('path');
5 |
6 | const Code = require('@hapi/code');
7 | const ESLint = require('eslint');
8 | const Lab = require('@hapi/lab');
9 |
10 | const HapiPlugin = require('../..');
11 | const CommonTestCases = require('./common');
12 |
13 | const internals = {};
14 |
15 |
16 | const { describe, it } = exports.lab = Lab.script();
17 | const expect = Code.expect;
18 |
19 |
20 | Code.settings.truncateMessages = false;
21 |
22 |
23 | internals.lintFile = async function (file) {
24 |
25 | const cli = new ESLint.ESLint({
26 | overrideConfigFile: true,
27 | baseConfig: [...HapiPlugin.configs.recommended]
28 | });
29 |
30 | const data = await Fs.promises.readFile(Path.join(__dirname, file), 'utf8');
31 | return await cli.lintText(data);
32 | };
33 |
34 |
35 | describe('recommended config', () => {
36 |
37 | CommonTestCases(expect, it, internals.lintFile);
38 |
39 | it('doesn\'t parse private class fields', async () => {
40 |
41 | const output = await internals.lintFile('fixtures/private-class-field.js');
42 | const results = output[0];
43 |
44 | expect(results.errorCount).to.equal(1);
45 | expect(results.warningCount).to.equal(0);
46 |
47 | const msg = results.messages[0];
48 | expect(msg.ruleId).to.be.null();
49 | expect(msg.severity).to.equal(2);
50 | expect(msg.message).to.equal('Parsing error: Unexpected character \'#\'');
51 |
52 | expect(msg.line).to.equal(4);
53 | expect(msg.column).to.equal(5);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Code = require('@hapi/code');
4 | const Lab = require('@hapi/lab');
5 | const Plugin = require('..');
6 |
7 |
8 | const internals = {};
9 |
10 |
11 | const { describe, it } = exports.lab = Lab.script();
12 | const expect = Code.expect;
13 |
14 |
15 | Code.settings.truncateMessages = false;
16 |
17 |
18 | describe('ESLint Plugin', () => {
19 |
20 | it('exposes all expected rules', () => {
21 |
22 | expect(Plugin.rules).to.exist();
23 | expect(Plugin.rules).to.be.an.object();
24 |
25 | const rules = Object.keys(Plugin.rules);
26 |
27 | expect(rules.length).to.equal(5);
28 | expect(rules.includes('capitalize-modules')).to.be.true();
29 | expect(rules.includes('for-loop')).to.be.true();
30 | expect(rules.includes('no-var')).to.be.true();
31 | expect(rules.includes('scope-start')).to.be.true();
32 | expect(rules.includes('no-arrowception')).to.be.true();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/rules/capitalize-modules.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Code = require('@hapi/code');
4 | const ESLint = require('eslint');
5 | const Lab = require('@hapi/lab');
6 | const Rule = require('../../lib/rules/capitalize-modules');
7 |
8 |
9 | const { describe, it } = exports.lab = Lab.script();
10 |
11 |
12 | Code.settings.truncateMessages = false;
13 |
14 |
15 | describe('capitalize-modules rule', () => {
16 |
17 | it('reports warning when module is not capitalized', () => {
18 |
19 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
20 | const sample = [
21 | 'const hapi = require("hapi");',
22 | 'let poop; poop = require("poop");',
23 | 'const foo = {bar: function() { const hapi = require("hapi"); }};'
24 | ];
25 |
26 | ruleTester.run('test', Rule, {
27 | valid: [],
28 | invalid: sample.map((code) => {
29 |
30 | return {
31 | code,
32 | errors: [{ message: 'Imported module variable name not capitalized.' }]
33 | };
34 | })
35 | });
36 | });
37 |
38 | it('does not report anything if module variable is capitalized', () => {
39 |
40 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
41 | const sample = [
42 | 'const Hapi = require("hapi");',
43 | 'let Poop; Poop = require("poop");',
44 | 'Code = require("code");'
45 | ];
46 |
47 | ruleTester.run('test', Rule, {
48 | valid: sample.map((code) => {
49 |
50 | return { code };
51 | }),
52 | invalid: []
53 | });
54 | });
55 |
56 | it('only warns on globals when global-scope-only is set', () => {
57 |
58 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
59 | const valid = [
60 | 'function foo() { const hapi = require("hapi"); }',
61 | 'const foo = function() { const hapi = require("hapi"); }',
62 | 'const foo = {bar: function() { hapi = require("hapi"); }};'
63 | ];
64 |
65 | const invalid = [
66 | 'hapi = require("hapi");',
67 | 'let poop; poop = require("poop");'
68 | ];
69 |
70 | ruleTester.run('test', Rule, {
71 | valid: valid.map((code) => {
72 |
73 | return {
74 | code,
75 | options: ['global-scope-only']
76 | };
77 | }),
78 | invalid: invalid.map((code) => {
79 |
80 | return {
81 | code,
82 | options: ['global-scope-only'],
83 | errors: [{ message: 'Imported module variable name not capitalized.' }]
84 | };
85 | })
86 | });
87 | });
88 |
89 | it('global-scope-only works in the presense of ES6 modules', () => {
90 |
91 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
92 | const invalid = [
93 | 'hapi = require("hapi");',
94 | 'let poop; poop = require("poop");'
95 | ];
96 |
97 | ruleTester.run('test', Rule, {
98 | valid: [],
99 | invalid: invalid.map((code) => {
100 |
101 | return {
102 | code,
103 | options: ['global-scope-only'],
104 | errors: [{ message: 'Imported module variable name not capitalized.' }]
105 | };
106 | })
107 | });
108 | });
109 |
110 | it('does not report anything for non-module variables', () => {
111 |
112 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
113 | const sample = [
114 | 'let foo, bar, baz;',
115 | 'const foo = fn()',
116 | 'const foo = "string";',
117 | 'const foo = this.bar()',
118 | 'foo[bar] = 5;',
119 | 'this.foo = null;',
120 | '[foo, bar] = [1, 2];',
121 | '[foo, bar] = require("baz");',
122 | 'const {foo} = require("bar");'
123 | ];
124 |
125 | ruleTester.run('test', Rule, {
126 | valid: sample.map((code) => {
127 |
128 | return { code };
129 | }),
130 | invalid: []
131 | });
132 | });
133 | });
134 |
--------------------------------------------------------------------------------
/test/rules/for-loop.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Code = require('@hapi/code');
4 | const ESLint = require('eslint');
5 | const Lab = require('@hapi/lab');
6 | const Rule = require('../../lib/rules/for-loop');
7 |
8 |
9 | const internals = {};
10 |
11 |
12 | const { describe, it } = exports.lab = Lab.script();
13 |
14 |
15 | Code.settings.truncateMessages = false;
16 |
17 |
18 | describe('for-loop rule', () => {
19 |
20 | it('enforces iterator variable naming', () => {
21 |
22 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
23 | const valids = [
24 | {
25 | code: 'for (let i = 0; i < a.length; ++i) { for (let j = 0; j < b.length; ++j) {} }'
26 | },
27 | {
28 | code: 'for (let j = 0; j < a.length; ++j) { for (let k = 0; k < b.length; ++k) {} }',
29 | options: [{ startIterator: 'j' }]
30 | },
31 | {
32 | code: 'for (let i = 0; i < a.length; ++i) {}; for (let i = 0; i < a.length; ++i) {}'
33 | },
34 | {
35 | code: 'for (;;) {}'
36 | }
37 | ];
38 |
39 | const invalids = [
40 | {
41 | code: 'for (let j = 0; j < a.length; ++j) {}',
42 | errors: [{ message: 'Expected iterator \'i\', but got \'j\'.' }]
43 | },
44 | {
45 | code: 'for (let i = 0; i < a.length; ++i) {}',
46 | options: [{ startIterator: 'j' }],
47 | errors: [{ message: 'Expected iterator \'j\', but got \'i\'.' }]
48 | }
49 | ];
50 |
51 | ruleTester.run('test', Rule, {
52 | valid: valids,
53 | invalid: invalids
54 | });
55 | });
56 |
57 | it('enforces a maximum of one variable initialized per loop', () => {
58 |
59 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
60 | const valids = [
61 | {
62 | code: 'for (let i = 0; i < a.length; ++i) {}'
63 | },
64 | {
65 | code: 'for (i = 0, j = 1; i < a.length; ++i) {}'
66 | },
67 | {
68 | code: 'for (; i < a.length; ++i) {}'
69 | }
70 | ];
71 |
72 | const invalids = [
73 | {
74 | code: 'for (let i = 0, j; i < a.length; ++i) {}',
75 | errors: [{ message: 'Only one variable can be initialized per loop.' }]
76 | },
77 | {
78 | code: 'for (let [i] = [0]; i < a.length; ++i) {}',
79 | errors: [{ message: 'Left hand side of initializer must be a single variable.' }]
80 | }
81 | ];
82 |
83 | ruleTester.run('test', Rule, {
84 | valid: valids,
85 | invalid: invalids
86 | });
87 | });
88 |
89 | it('enforces the maximum number of nested for loops', () => {
90 |
91 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
92 | const valids = [
93 | {
94 | code: 'for (let i = 0; i < a.length; ++i) {}'
95 | },
96 | {
97 | code: 'for (let i = 0; i < a.length; ++i) { for (let j = 0; j < b.length; ++j) { for (let k = 0; k < c.length; ++k) { for (let l = 0; l < d.length; ++l) {} } } }',
98 | options: [{ maxDepth: 4 }]
99 | }
100 | ];
101 |
102 | const invalids = [
103 | {
104 | code: 'for (let i = 0; i < a.length; ++i) { for (let j = 0; j < b.length; ++j) { for (let k = 0; k < c.length; ++k) { for (let l = 0; l < d.length; ++l) {} } } }',
105 | errors: [{ message: 'Too many nested for loops.' }]
106 | },
107 | {
108 | code: 'for (let i = 0; i < a.length; ++i) { for (let j = 0; j < b.length; ++j) {} }',
109 | options: [{ maxDepth: 1 }],
110 | errors: [{ message: 'Too many nested for loops.' }]
111 | }
112 | ];
113 |
114 | ruleTester.run('test', Rule, {
115 | valid: valids,
116 | invalid: invalids
117 | });
118 | });
119 |
120 | it('prevents post-increment and post-decrement', () => {
121 |
122 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
123 | const valids = [
124 | {
125 | code: 'for (let i = 0; i < a.length; ++i) {}'
126 | },
127 | {
128 | code: 'for (let i = 0; i < a.length; --i) {}'
129 | },
130 | {
131 | code: 'for (let i = 0; i < a.length; i += 1) {}'
132 | },
133 | {
134 | code: 'for (let i = 0; i < a.length; i = i + 1) {}'
135 | },
136 | {
137 | code: 'for (let i = 0; i < a.length;) {}'
138 | }
139 | ];
140 |
141 | const invalids = [
142 | {
143 | code: 'for (let i = 0; i < a.length; i++) {}',
144 | errors: [{ message: 'Update to iterator should use prefix operator.' }]
145 | },
146 | {
147 | code: 'for (let i = 0; i < a.length; i--) {}',
148 | errors: [{ message: 'Update to iterator should use prefix operator.' }]
149 | }
150 | ];
151 |
152 | ruleTester.run('test', Rule, {
153 | valid: valids,
154 | invalid: invalids
155 | });
156 | });
157 | });
158 |
--------------------------------------------------------------------------------
/test/rules/no-arrowception.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Code = require('@hapi/code');
4 | const ESLint = require('eslint');
5 | const Lab = require('@hapi/lab');
6 | const Rule = require('../../lib/rules/no-arrowception');
7 |
8 |
9 | const internals = {};
10 |
11 |
12 | const { describe, it } = exports.lab = Lab.script();
13 |
14 |
15 | Code.settings.truncateMessages = false;
16 |
17 |
18 | describe('no-arrowception rule', () => {
19 |
20 | it('reports error when an arrow function implicitly creates another arrow function', () => {
21 |
22 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
23 | const valids = [
24 | 'const foo = () => 85;',
25 | 'const foo = () => { return 42; }',
26 | 'const foo = () => ({});',
27 | 'const foo = () => ({\nbar: 1});',
28 | 'const foo = () => [];',
29 | 'const foo = () => [\n1,\n2];',
30 | 'const foo = () => { return () => 85; };'
31 | ].map((code) => {
32 |
33 | return { code };
34 | });
35 |
36 | const invalids = [
37 | 'const foo = () => () => 85;'
38 | ].map((code) => {
39 |
40 | return {
41 | code,
42 | errors: [{ message: 'Arrow function implicitly creates arrow function.' }]
43 | };
44 | });
45 |
46 | ruleTester.run('test', Rule, {
47 | valid: valids,
48 | invalid: invalids
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/test/rules/no-var.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Code = require('@hapi/code');
4 | const ESLint = require('eslint');
5 | const Lab = require('@hapi/lab');
6 | const Rule = require('../../lib/rules/no-var');
7 |
8 |
9 | const { describe, it } = exports.lab = Lab.script();
10 | const RuleTester = ESLint.RuleTester;
11 |
12 |
13 | Code.settings.truncateMessages = false;
14 |
15 |
16 | describe('no-var rule', () => {
17 |
18 | it('reports warning when vars used outside of try...catch scope', () => {
19 |
20 | const sample = [
21 | 'function test() { var a = 1; }',
22 | 'function test() { try { var bf = 2; console.log(bf); } catch (err) {} }',
23 | 'function test() { try {} catch (err) { var cf = 3; console.log(cf); } }',
24 | 'function test() { try { var bf = 2; if (bf) { console.log(bf); } } catch (err) {} }',
25 | 'function test() { try { if (true) { var bf = 2; } console.log(bf); } catch (err) {} }',
26 | 'var a = 1; try {} catch (err) {}'
27 | ];
28 |
29 | const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } });
30 | ruleTester.run('test', Rule, {
31 | valid: [],
32 | invalid: sample.map((code) => {
33 |
34 | return {
35 | code,
36 | errors: [{ message: 'Unexpected var, use let or const instead.' }]
37 | };
38 | })
39 | });
40 | });
41 |
42 | it('ignores vars used inside try...catch scope and referenced from outside', () => {
43 |
44 | const sample = [
45 | 'const a = 1;',
46 | 'function test() { try { var bf = 2; } catch (err) {} console.log(bf); }',
47 | 'function test() { try {} catch (err) { var cf = 3; } console.log(cf); }',
48 | 'function test() { a = 1; try { var a = 2; } catch (err) {} }',
49 | 'try { var a = 1; } catch (err) {} console.log(a);'
50 | ];
51 |
52 | const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } });
53 | ruleTester.run('test', Rule, {
54 | valid: sample.map((code) => {
55 |
56 | return { code };
57 | }),
58 | invalid: []
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/rules/scope-start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Code = require('@hapi/code');
4 | const ESLint = require('eslint');
5 | const Lab = require('@hapi/lab');
6 | const Rule = require('../../lib/rules/scope-start');
7 |
8 |
9 | const { describe, it } = exports.lab = Lab.script();
10 |
11 |
12 | Code.settings.truncateMessages = false;
13 |
14 |
15 | describe('scope-start rule', () => {
16 |
17 | it('reports warning when function body does not begin with a blank line', () => {
18 |
19 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
20 | const invalids = [
21 | `function fn() {
22 | return;
23 | }`,
24 | `function fn(foo, bar, baz) {
25 | const fizz = 1;
26 | }`,
27 | `function fn(foo) {
28 | return 'foo';
29 | }`,
30 | `function fn() {/*test*/
31 | return;
32 | }`,
33 | 'function fn() { return; }',
34 | 'function fn(foo, bar, baz) { return; }'
35 | ];
36 |
37 | ruleTester.run('test', Rule, {
38 | valid: [],
39 | invalid: invalids.map((code) => {
40 |
41 | return {
42 | code,
43 | errors: [{ message: 'Missing blank line at beginning of function.' }]
44 | };
45 | })
46 | });
47 | });
48 |
49 | it('does not report anything when function body begins with a blank line', () => {
50 |
51 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
52 | const valids = [
53 | `function fn() {
54 |
55 | return;
56 | }`,
57 | `function fn(foo, bar, baz) {
58 |
59 | const fizz = 1;
60 | }`,
61 | `function fn(foo) {
62 |
63 | return 'foo';
64 | }`,
65 | `function fn() {/*test*/
66 |
67 | return;
68 | }`
69 | ];
70 |
71 | ruleTester.run('test', Rule, {
72 | valid: valids.map((code) => {
73 |
74 | return { code };
75 | }),
76 | invalid: []
77 | });
78 | });
79 |
80 | it('does not report anything when function is one line and allow-one-liners is set', () => {
81 |
82 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
83 | const valids = [
84 | 'function fn() { return; }',
85 | 'function fn(foo, bar, baz) { return; }'
86 | ];
87 |
88 | ruleTester.run('test', Rule, {
89 | valid: valids.map((code) => {
90 |
91 | return {
92 | code,
93 | options: ['allow-one-liners']
94 | };
95 | }),
96 | invalid: []
97 | });
98 | });
99 |
100 | it('reports an error when function is allow-one-liners is set but function body contains too many statements', () => {
101 |
102 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
103 | const invalids = [
104 | 'function fn() { let i = 0; i++; return; }'
105 | ];
106 |
107 | ruleTester.run('test', Rule, {
108 | valid: [],
109 | invalid: invalids.map((code) => {
110 |
111 | return {
112 | code,
113 | options: ['allow-one-liners', 2],
114 | errors: [{ message: 'Missing blank line at beginning of function.' }]
115 | };
116 | })
117 | });
118 | });
119 |
120 | it('allow-one-liners defaults to 1', () => {
121 |
122 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
123 | const invalids = [
124 | 'function fn() { console.log(\'broken\'); return; }'
125 | ];
126 |
127 | ruleTester.run('test', Rule, {
128 | valid: [],
129 | invalid: invalids.map((code) => {
130 |
131 | return {
132 | code,
133 | options: ['allow-one-liners'],
134 | errors: [{ message: 'Missing blank line at beginning of function.' }]
135 | };
136 | })
137 | });
138 | });
139 |
140 | it('does not report anything when function body is empty', () => {
141 |
142 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
143 | const valids = [
144 | 'function fn() { }',
145 | 'function fn(foo, bar, baz) { }',
146 | `function fn(foo) {
147 |
148 | }`,
149 | 'function fn() {/*test*/ }'
150 | ];
151 |
152 | ruleTester.run('test', Rule, {
153 | valid: valids.map((code) => {
154 |
155 | return { code };
156 | }),
157 | invalid: []
158 | });
159 | });
160 |
161 | it('handles function expressions', () => {
162 |
163 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
164 |
165 | const code = `const foo = function () {
166 |
167 | return;
168 | }`;
169 |
170 | ruleTester.run('test', Rule, {
171 | valid: [{ code }],
172 | invalid: []
173 | });
174 | });
175 |
176 | it('handles arrow functions', () => {
177 |
178 | const ruleTester = new ESLint.RuleTester({ languageOptions: { ecmaVersion: 2019 } });
179 | const valids = [
180 | 'const foo = () => {\n\nreturn;};',
181 | 'const foo = () => {\n\nreturn;}',
182 | 'const foo = () => 42;',
183 | 'const foo = () => 42\n',
184 | 'const foo = () => ({});',
185 | 'const foo = () => ({})',
186 | 'const foo = () => ({\nbar: 1});',
187 | 'const foo = () => [];',
188 | 'const foo = () => [\n1,\n2];',
189 | 'const foo = (isTrue) ? () => bar()\n: false;',
190 | 'const foo = (isTrue) ? true:\n () => 1;'
191 | ].map((code) => {
192 |
193 | return { code };
194 | });
195 |
196 | const invalids = [
197 | 'const foo = () => {\nreturn;};',
198 | 'const foo = () => {const foo = 1; return foo;};',
199 | 'const foo = () => {const foo = 1;\nreturn foo;};',
200 | 'const foo = () => \n12;',
201 | 'const foo = () => "1" + \n"2";'
202 | ].map((code) => {
203 |
204 | return {
205 | code,
206 | errors: [{ message: 'Missing blank line at beginning of function.' }]
207 | };
208 | });
209 |
210 | ruleTester.run('test', Rule, {
211 | valid: valids,
212 | invalid: invalids
213 | });
214 | });
215 | });
216 |
--------------------------------------------------------------------------------