@tianjos/eslint-plugin-elegant 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +385 -3
- package/dist/index.d.ts +44 -0
- package/dist/index.js +32 -0
- package/dist/rules/max-class-dependencies.d.ts +9 -0
- package/dist/rules/max-class-dependencies.js +112 -0
- package/dist/rules/max-class-fields.d.ts +9 -0
- package/dist/rules/max-class-fields.js +81 -0
- package/dist/rules/max-class-methods.js +2 -3
- package/dist/rules/max-returns.d.ts +7 -0
- package/dist/rules/max-returns.js +86 -0
- package/dist/rules/no-anonymous-param-type.d.ts +7 -0
- package/dist/rules/no-anonymous-param-type.js +120 -0
- package/dist/rules/no-comments-in-function-body.d.ts +7 -0
- package/dist/rules/no-comments-in-function-body.js +85 -0
- package/dist/rules/no-else-after-throw.d.ts +4 -0
- package/dist/rules/no-else-after-throw.js +48 -0
- package/dist/rules/no-interpolated-log-message.d.ts +8 -0
- package/dist/rules/no-interpolated-log-message.js +87 -0
- package/dist/rules/no-logic-in-constructor.js +6 -7
- package/dist/rules/no-null-return.js +3 -4
- package/dist/rules/no-null.js +8 -4
- package/dist/rules/no-property-alias.d.ts +7 -0
- package/dist/rules/no-property-alias.js +67 -0
- package/dist/rules/no-property-destructuring.d.ts +4 -0
- package/dist/rules/no-property-destructuring.js +59 -0
- package/dist/rules/no-public-mutable-props.js +7 -3
- package/dist/utils/locals.d.ts +13 -0
- package/dist/utils/locals.js +26 -0
- package/dist/utils/memberChain.d.ts +8 -0
- package/dist/utils/memberChain.js +22 -0
- package/package.json +3 -2
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const OBJECTS = ['log', 'logger'];
|
|
6
|
+
const METHODS = [
|
|
7
|
+
'debug',
|
|
8
|
+
'error',
|
|
9
|
+
'fatal',
|
|
10
|
+
'info',
|
|
11
|
+
'log',
|
|
12
|
+
'trace',
|
|
13
|
+
'verbose',
|
|
14
|
+
'warn',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* The name a call hangs off, as written. `logger.info` yields `logger`, and
|
|
18
|
+
* `this.logger.info` yields `logger` too, so a field and a local read alike.
|
|
19
|
+
*/
|
|
20
|
+
const receiver = (node) => {
|
|
21
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
22
|
+
return node.name;
|
|
23
|
+
}
|
|
24
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
25
|
+
!node.computed &&
|
|
26
|
+
node.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
27
|
+
return node.property.name;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The message is the first argument that is not an object literal, which lands
|
|
33
|
+
* on the message under either convention: `info(msg, data)` as in Nest and
|
|
34
|
+
* winston, and `info(data, msg)` as in pino.
|
|
35
|
+
*/
|
|
36
|
+
const message = (args) => args.find((arg) => arg.type !== utils_1.AST_NODE_TYPES.ObjectExpression);
|
|
37
|
+
const computed = (node) => (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
38
|
+
node.expressions.length > 0) ||
|
|
39
|
+
(node.type === utils_1.AST_NODE_TYPES.BinaryExpression && node.operator === '+');
|
|
40
|
+
exports.default = (0, createRule_1.createRule)({
|
|
41
|
+
name: 'no-interpolated-log-message',
|
|
42
|
+
meta: {
|
|
43
|
+
type: 'suggestion',
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Require log messages to be constant, with the varying parts passed as structured data.',
|
|
46
|
+
},
|
|
47
|
+
messages: {
|
|
48
|
+
interpolatedMessage: 'A computed log message cannot be grouped or searched. Keep the message constant and pass the varying parts as structured data.',
|
|
49
|
+
},
|
|
50
|
+
schema: [
|
|
51
|
+
{
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
objects: { type: 'array', items: { type: 'string' } },
|
|
55
|
+
methods: { type: 'array', items: { type: 'string' } },
|
|
56
|
+
},
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
defaultOptions: [{ objects: [], methods: [] }],
|
|
62
|
+
create(context, [{ objects, methods }]) {
|
|
63
|
+
const logging = new Set([...OBJECTS, ...objects]);
|
|
64
|
+
const levels = new Set([...METHODS, ...methods]);
|
|
65
|
+
return {
|
|
66
|
+
CallExpression(node) {
|
|
67
|
+
if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
68
|
+
node.callee.computed ||
|
|
69
|
+
node.callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
70
|
+
!levels.has(node.callee.property.name)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const name = receiver(node.callee.object);
|
|
74
|
+
if (name === undefined || !logging.has(name)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const argument = message(node.arguments);
|
|
78
|
+
if (argument !== undefined && computed(argument)) {
|
|
79
|
+
context.report({
|
|
80
|
+
node: argument,
|
|
81
|
+
messageId: 'interpolatedMessage',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -49,19 +49,18 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
49
49
|
context.report({ node: statement, messageId: 'statement' });
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
if (isSuperCall(expression)) {
|
|
52
|
+
if (isSuperCall(statement.expression)) {
|
|
54
53
|
continue;
|
|
55
54
|
}
|
|
56
|
-
if (expression.type !== utils_1.AST_NODE_TYPES.AssignmentExpression ||
|
|
57
|
-
expression.operator !== '=' ||
|
|
58
|
-
!isThisMember(expression.left)) {
|
|
55
|
+
if (statement.expression.type !== utils_1.AST_NODE_TYPES.AssignmentExpression ||
|
|
56
|
+
statement.expression.operator !== '=' ||
|
|
57
|
+
!isThisMember(statement.expression.left)) {
|
|
59
58
|
context.report({ node: statement, messageId: 'statement' });
|
|
60
59
|
continue;
|
|
61
60
|
}
|
|
62
|
-
if (!isPlainValue(expression.right)) {
|
|
61
|
+
if (!isPlainValue(statement.expression.right)) {
|
|
63
62
|
context.report({
|
|
64
|
-
node: expression.right,
|
|
63
|
+
node: statement.expression.right,
|
|
65
64
|
messageId: 'computation',
|
|
66
65
|
});
|
|
67
66
|
}
|
|
@@ -18,10 +18,9 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
18
18
|
create(context) {
|
|
19
19
|
return {
|
|
20
20
|
ReturnStatement(node) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
argument.
|
|
24
|
-
argument.raw === 'null') {
|
|
21
|
+
if (node.argument?.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
22
|
+
node.argument.value === null &&
|
|
23
|
+
node.argument.raw === 'null') {
|
|
25
24
|
context.report({ node, messageId: 'noNullReturn' });
|
|
26
25
|
}
|
|
27
26
|
},
|
package/dist/rules/no-null.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const utils_1 = require("@typescript-eslint/utils");
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
/**
|
|
6
|
+
* A `null` sitting directly in a `return`. That shape is the domain of
|
|
7
|
+
* `no-null-return`, which reports it with its own message; reporting it here
|
|
8
|
+
* too would double up on one line of code.
|
|
9
|
+
*/
|
|
10
|
+
const isDirectReturn = (node) => node.parent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
11
|
+
node.parent.argument === node;
|
|
5
12
|
exports.default = (0, createRule_1.createRule)({
|
|
6
13
|
name: 'no-null',
|
|
7
14
|
meta: {
|
|
@@ -21,10 +28,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
21
28
|
if (node.value !== null || node.raw !== 'null') {
|
|
22
29
|
return;
|
|
23
30
|
}
|
|
24
|
-
|
|
25
|
-
const { parent } = node;
|
|
26
|
-
if (parent.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
27
|
-
parent.argument === node) {
|
|
31
|
+
if (isDirectReturn(node)) {
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
context.report({ node, messageId: 'noNull' });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type Options = [{
|
|
2
|
+
allowEnv: boolean;
|
|
3
|
+
}];
|
|
4
|
+
declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"aliasesProperty", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const locals_1 = require("../utils/locals");
|
|
6
|
+
const memberChain_1 = require("../utils/memberChain");
|
|
7
|
+
const DEFAULT_ALLOW_ENV = true;
|
|
8
|
+
/** Whether the chain reads `process.env.SOMETHING`. */
|
|
9
|
+
const isEnvRead = (node) => node.object.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
10
|
+
!node.object.computed &&
|
|
11
|
+
node.object.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
12
|
+
node.object.object.name === 'process' &&
|
|
13
|
+
node.object.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
14
|
+
node.object.property.name === 'env';
|
|
15
|
+
exports.default = (0, createRule_1.createRule)({
|
|
16
|
+
name: 'no-property-alias',
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
docs: {
|
|
20
|
+
description: 'Disallow locals that only rename a property of an object already in hand. Ask the object where the value is needed instead.',
|
|
21
|
+
},
|
|
22
|
+
messages: {
|
|
23
|
+
aliasesProperty: "'{{name}}' only renames '{{path}}'. Ask the object where you need the value instead of copying its state into a local.",
|
|
24
|
+
},
|
|
25
|
+
schema: [
|
|
26
|
+
{
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
allowEnv: { type: 'boolean' },
|
|
30
|
+
},
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
defaultOptions: [{ allowEnv: DEFAULT_ALLOW_ENV }],
|
|
36
|
+
create(context, [{ allowEnv }]) {
|
|
37
|
+
return {
|
|
38
|
+
VariableDeclarator(node) {
|
|
39
|
+
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (node.init === null ||
|
|
43
|
+
node.init.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
44
|
+
!(0, memberChain_1.isObjectInHand)(node.init)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (allowEnv && isEnvRead(node.init)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const [variable] = context.sourceCode.getDeclaredVariables(node);
|
|
51
|
+
if (variable === undefined ||
|
|
52
|
+
(0, locals_1.isReassigned)(variable) ||
|
|
53
|
+
(0, locals_1.escapesIntoFunction)(variable)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
context.report({
|
|
57
|
+
node,
|
|
58
|
+
messageId: 'aliasesProperty',
|
|
59
|
+
data: {
|
|
60
|
+
name: node.id.name,
|
|
61
|
+
path: context.sourceCode.getText(node.init),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const locals_1 = require("../utils/locals");
|
|
6
|
+
const memberChain_1 = require("../utils/memberChain");
|
|
7
|
+
/**
|
|
8
|
+
* Whether the pattern collects the properties it does not name into a rest
|
|
9
|
+
* object. That is construction by omission, not a copy of state anyone can
|
|
10
|
+
* replace with a member access.
|
|
11
|
+
*/
|
|
12
|
+
const hasRestElement = (node) => node.properties.some((property) => property.type === utils_1.AST_NODE_TYPES.RestElement);
|
|
13
|
+
/**
|
|
14
|
+
* Whether any property carries a default. `options.max ?? 3` repeats the
|
|
15
|
+
* fallback at every use site, so the pattern is holding a decision rather than
|
|
16
|
+
* just a copy.
|
|
17
|
+
*/
|
|
18
|
+
const hasDefault = (node) => node.properties.some((property) => property.type === utils_1.AST_NODE_TYPES.Property &&
|
|
19
|
+
property.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern);
|
|
20
|
+
exports.default = (0, createRule_1.createRule)({
|
|
21
|
+
name: 'no-property-destructuring',
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'suggestion',
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Disallow destructuring an object already in hand. Ask the object where each value is needed instead.',
|
|
26
|
+
},
|
|
27
|
+
messages: {
|
|
28
|
+
destructuresObject: "Destructuring '{{name}}' copies its state into {{count}} locals. Ask the object where you need each value instead.",
|
|
29
|
+
},
|
|
30
|
+
schema: [],
|
|
31
|
+
},
|
|
32
|
+
defaultOptions: [],
|
|
33
|
+
create(context) {
|
|
34
|
+
return {
|
|
35
|
+
ObjectPattern(node) {
|
|
36
|
+
if (node.parent.type !== utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
37
|
+
node.parent.init === null ||
|
|
38
|
+
!(0, memberChain_1.isObjectInHand)(node.parent.init)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (hasRestElement(node) || hasDefault(node)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const variables = context.sourceCode.getDeclaredVariables(node.parent);
|
|
45
|
+
if (variables.some((variable) => (0, locals_1.isReassigned)(variable) || (0, locals_1.escapesIntoFunction)(variable))) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
context.report({
|
|
49
|
+
node: node.parent,
|
|
50
|
+
messageId: 'destructuresObject',
|
|
51
|
+
data: {
|
|
52
|
+
name: context.sourceCode.getText(node.parent.init),
|
|
53
|
+
count: node.properties.length,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -12,6 +12,12 @@ const keyName = (key) => {
|
|
|
12
12
|
}
|
|
13
13
|
return 'property';
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* A constructor parameter property the outside world can write to. Only
|
|
17
|
+
* constructor parameter properties carry an accessibility modifier, so reading
|
|
18
|
+
* one is enough to know the field is public.
|
|
19
|
+
*/
|
|
20
|
+
const isPublicMutable = (node) => node.accessibility === 'public' && !node.readonly;
|
|
15
21
|
exports.default = (0, createRule_1.createRule)({
|
|
16
22
|
name: 'no-public-mutable-props',
|
|
17
23
|
meta: {
|
|
@@ -38,9 +44,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
38
44
|
});
|
|
39
45
|
},
|
|
40
46
|
TSParameterProperty(node) {
|
|
41
|
-
|
|
42
|
-
if (node.accessibility !== 'public' ||
|
|
43
|
-
node.readonly) {
|
|
47
|
+
if (!isPublicMutable(node)) {
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
50
|
const target = node.parameter.type === utils_1.AST_NODE_TYPES.AssignmentPattern
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Whether anything writes to the variable after its declaration. Such a local
|
|
4
|
+
* holds mutable state that no member access stands in for, so it is not a copy
|
|
5
|
+
* of the object's state to begin with.
|
|
6
|
+
*/
|
|
7
|
+
export declare const isReassigned: (variable: TSESLint.Scope.Variable) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Whether the variable is read from inside a nested function. TypeScript drops
|
|
10
|
+
* a narrowing of `obj.prop` at the callback boundary but keeps it on a local,
|
|
11
|
+
* so such a declaration is load-bearing: inlining it stops compiling.
|
|
12
|
+
*/
|
|
13
|
+
export declare const escapesIntoFunction: (variable: TSESLint.Scope.Variable) => boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.escapesIntoFunction = exports.isReassigned = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Whether anything writes to the variable after its declaration. Such a local
|
|
6
|
+
* holds mutable state that no member access stands in for, so it is not a copy
|
|
7
|
+
* of the object's state to begin with.
|
|
8
|
+
*/
|
|
9
|
+
const isReassigned = (variable) => variable.references.some((reference) => reference.isWrite() && !reference.init);
|
|
10
|
+
exports.isReassigned = isReassigned;
|
|
11
|
+
/**
|
|
12
|
+
* Whether the variable is read from inside a nested function. TypeScript drops
|
|
13
|
+
* a narrowing of `obj.prop` at the callback boundary but keeps it on a local,
|
|
14
|
+
* so such a declaration is load-bearing: inlining it stops compiling.
|
|
15
|
+
*/
|
|
16
|
+
const escapesIntoFunction = (variable) => variable.references.some((reference) => {
|
|
17
|
+
let scope = reference.from;
|
|
18
|
+
while (scope !== null && scope !== variable.scope) {
|
|
19
|
+
if (scope.type === 'function') {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
scope = scope.upper;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
26
|
+
exports.escapesIntoFunction = escapesIntoFunction;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the expression denotes an object the reader already holds: a name,
|
|
4
|
+
* `this`, or a run of plain `.prop` accesses rooted at one of those. A computed
|
|
5
|
+
* link (`calls[1][0].metadata`) or a call in the middle
|
|
6
|
+
* (`resolveDates(query).startDate`) fails — nobody gains by inlining those.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isObjectInHand: (node: TSESTree.Node) => boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isObjectInHand = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
/**
|
|
6
|
+
* Whether the expression denotes an object the reader already holds: a name,
|
|
7
|
+
* `this`, or a run of plain `.prop` accesses rooted at one of those. A computed
|
|
8
|
+
* link (`calls[1][0].metadata`) or a call in the middle
|
|
9
|
+
* (`resolveDates(query).startDate`) fails — nobody gains by inlining those.
|
|
10
|
+
*/
|
|
11
|
+
const isObjectInHand = (node) => {
|
|
12
|
+
let current = node;
|
|
13
|
+
while (current.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
14
|
+
if (current.computed) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
current = current.object;
|
|
18
|
+
}
|
|
19
|
+
return (current.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
20
|
+
current.type === utils_1.AST_NODE_TYPES.ThisExpression);
|
|
21
|
+
};
|
|
22
|
+
exports.isObjectInHand = isObjectInHand;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tianjos/eslint-plugin-elegant",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Opinionated ESLint rules for elegant, behavior-rich TypeScript:
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Opinionated ESLint rules for elegant, behavior-rich TypeScript: honest types, encapsulated state, small uncoupled classes, guard-clause flow, and structured logging. Built for NestJS and DDD codebases.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
7
7
|
"eslint-plugin",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc -p tsconfig.json",
|
|
44
|
+
"lint": "npm run build && eslint .",
|
|
44
45
|
"typecheck": "tsc -p tsconfig.test.json",
|
|
45
46
|
"test": "jest",
|
|
46
47
|
"release": "standard-version",
|