rev-dep 1.0.0-alpha.1 → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/babel.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/babel/index.js')
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ /*eslint-disable @typescript-eslint/no-var-requires */
3
+ const node_path = require('path');
4
+ const fs = require('fs');
5
+ const parser = require('@babel/parser');
6
+ const template = require('@babel/template').default;
7
+ const SKIP = Symbol('SKIP');
8
+ module.exports = function plugin({ types }, { tsConfigPath }) {
9
+ const tsConfig = require(tsConfigPath);
10
+ const aliases = tsConfig.compilerOptions.paths;
11
+ const aliasesKeys = Object.keys(aliases);
12
+ const aliasesRegexes = Object.keys(aliases).map((alias) => {
13
+ return new RegExp(`^${alias.replace('*', '(.)+')}$`);
14
+ });
15
+ const cache = new Map();
16
+ const getFile = (original, paths) => {
17
+ if (paths.length === 0) {
18
+ throw new Error('Cannot resolve import ' + original);
19
+ }
20
+ const path = paths[0];
21
+ try {
22
+ return [path, fs.readFileSync(path).toString()];
23
+ }
24
+ catch (e) {
25
+ return getFile(original, paths.slice(1));
26
+ }
27
+ };
28
+ const isPathRelativeOrAliased = (path) => {
29
+ const aliasRegexIdx = aliasesRegexes.findIndex((aliasRegex) => aliasRegex.test(path));
30
+ const isRelative = path.startsWith('./') || path.startsWith('../');
31
+ return aliasRegexIdx > -1 || isRelative;
32
+ };
33
+ const cacheKey = (identifier, filePath) => `${identifier}-${filePath}`;
34
+ const lookup = (identifier, filePath, cwd) => {
35
+ const cached = cache.get(cacheKey(identifier, filePath));
36
+ if (cached) {
37
+ return cached;
38
+ }
39
+ const withExtension = /(\.ts|\.tsx)$/.test(filePath)
40
+ ? [filePath]
41
+ : [
42
+ `${filePath}.ts`,
43
+ `${filePath}.tsx`,
44
+ `${filePath}/index.ts`,
45
+ `${filePath}/index.tsx`,
46
+ `${filePath}.js`,
47
+ `${filePath}.jsx`,
48
+ `${filePath}/index.js`,
49
+ `${filePath}/index.jsx`
50
+ ];
51
+ const [resolvedFilePath, file] = getFile(filePath, withExtension);
52
+ const ast = parser.parse(file, {
53
+ sourceType: 'module',
54
+ plugins: [
55
+ 'jsx',
56
+ 'typescript',
57
+ 'objectRestSpread',
58
+ 'classProperties',
59
+ 'asyncGenerators',
60
+ 'decorators-legacy'
61
+ ]
62
+ });
63
+ /**
64
+ * {
65
+ * identifier?: string,
66
+ * source: string
67
+ * }
68
+ */
69
+ const toLookup = [];
70
+ let resolvedAs = null;
71
+ ast.program.body.forEach((declaration) => {
72
+ var _a;
73
+ if (resolvedAs === null) {
74
+ if (types.isExportNamedDeclaration(declaration)) {
75
+ if (types.isVariableDeclaration(declaration.declaration)) {
76
+ const hasIdentifier = declaration.declaration.declarations.find((declarator) => {
77
+ return declarator.id.name === identifier;
78
+ });
79
+ if (hasIdentifier) {
80
+ resolvedAs = {
81
+ type: 'named',
82
+ identifier,
83
+ source: filePath
84
+ };
85
+ }
86
+ }
87
+ else if (types.isFunctionDeclaration(declaration.declaration) ||
88
+ types.isClassDeclaration(declaration.declaration)) {
89
+ if (declaration.declaration.id.name === identifier) {
90
+ resolvedAs = {
91
+ type: 'named',
92
+ identifier,
93
+ source: filePath
94
+ };
95
+ }
96
+ }
97
+ else {
98
+ const source = (_a = declaration.source) === null || _a === void 0 ? void 0 : _a.value;
99
+ declaration.specifiers.forEach((specifier) => {
100
+ if (types.isExportSpecifier(specifier)) {
101
+ if (specifier.exported.name === identifier) {
102
+ if (specifier.local.name === 'default' && source) {
103
+ resolvedAs = {
104
+ type: 'default',
105
+ identifier,
106
+ source: getModulePath(source, resolvedFilePath, cwd)
107
+ };
108
+ }
109
+ else if (source === undefined) {
110
+ resolvedAs = {
111
+ type: 'named',
112
+ identifier,
113
+ source: filePath
114
+ };
115
+ }
116
+ else if (isPathRelativeOrAliased(source)) {
117
+ toLookup.push({
118
+ identifier: specifier.exported.local,
119
+ source: getModulePath(source, resolvedFilePath, cwd)
120
+ });
121
+ }
122
+ }
123
+ }
124
+ });
125
+ }
126
+ }
127
+ else if (types.isExportAllDeclaration(declaration) &&
128
+ isPathRelativeOrAliased(declaration.source.value)) {
129
+ toLookup.push({
130
+ identifier,
131
+ source: getModulePath(declaration.source.value, resolvedFilePath, cwd)
132
+ });
133
+ }
134
+ }
135
+ });
136
+ if (resolvedAs) {
137
+ return resolvedAs;
138
+ }
139
+ const nestedResult = toLookup
140
+ .map(({ identifier, source }) => lookup(identifier, source, cwd))
141
+ .filter(Boolean);
142
+ return nestedResult[0];
143
+ };
144
+ const getModulePath = (sourcePath, fileName, cwd) => {
145
+ var _a;
146
+ const aliasRegexIdx = aliasesRegexes.findIndex((aliasRegex) => aliasRegex.test(sourcePath));
147
+ const relativeFileName = node_path.relative(cwd, fileName);
148
+ const aliasKey = aliasesKeys[aliasRegexIdx];
149
+ const alias = (_a = aliases[aliasKey]) === null || _a === void 0 ? void 0 : _a[0];
150
+ let modulePath = '';
151
+ if (alias) {
152
+ let relative = alias;
153
+ if (aliasKey.endsWith('*')) {
154
+ const aliasKeyPrefix = aliasKey.replace('*', '');
155
+ relative = alias.replace('*', sourcePath.replace(aliasKeyPrefix, ''));
156
+ }
157
+ modulePath = node_path.resolve(cwd, relative);
158
+ }
159
+ else {
160
+ // we need ../ to skip current file name
161
+ modulePath = node_path.resolve(relativeFileName, '../' + sourcePath);
162
+ }
163
+ return modulePath;
164
+ };
165
+ return {
166
+ visitor: {
167
+ Program: {
168
+ enter(_, { filename }) {
169
+ //console.log(filename);
170
+ }
171
+ },
172
+ ImportDeclaration(path, { filename, cwd }) {
173
+ const sourceRelative = (source) => {
174
+ const rel = node_path.relative(node_path.dirname(filename), source);
175
+ return rel.startsWith('.') ? rel : './' + rel;
176
+ };
177
+ const node = path.node;
178
+ const source = node.source;
179
+ if (source.type !== 'StringLiteral') {
180
+ return;
181
+ }
182
+ const shouldSkip = node[SKIP] || !isPathRelativeOrAliased(source.value);
183
+ if (shouldSkip) {
184
+ return;
185
+ }
186
+ const modulePath = getModulePath(source.value, filename, cwd);
187
+ const defaultSpecifier = node.specifiers.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
188
+ const namespaceSpecifier = node.specifiers.find((specifier) => specifier.type === 'ImportNamespaceSpecifier');
189
+ const specifiers = node.specifiers.filter((specifier) => specifier.type === 'ImportSpecifier');
190
+ const results = specifiers.map((specifier) => {
191
+ const importedName = specifier.imported.name;
192
+ const result = lookup(importedName, modulePath, cwd);
193
+ if (!result) {
194
+ return {
195
+ identifier: importedName,
196
+ local: specifier.local.name,
197
+ source: source.value
198
+ };
199
+ }
200
+ cache.set(cacheKey(importedName, modulePath), result);
201
+ return {
202
+ ...result,
203
+ source: sourceRelative(result.source),
204
+ local: specifier.local.name
205
+ };
206
+ });
207
+ const defaultResult = defaultSpecifier
208
+ ? lookup('default', modulePath, cwd)
209
+ : null;
210
+ if (defaultResult) {
211
+ cache.set(cacheKey('default', modulePath), defaultResult);
212
+ }
213
+ const buildNamed = template(`
214
+ import { %%IMPORT_NAME%% } from %%SOURCE%%;
215
+ `);
216
+ const buildNamedWithAlias = template(`
217
+ import { %%IMPORTED_NAME%% as %%LOCAL_NAME%% } from %%SOURCE%%;
218
+ `);
219
+ const buildDefault = template(`
220
+ import %%IMPORT_NAME%% from %%SOURCE%%;
221
+ `);
222
+ const buildNamespace = template(`
223
+ import * as %%IMPORT_NAME%% from %%SOURCE%%;
224
+ `);
225
+ const defaultImport = defaultResult
226
+ ? [
227
+ buildDefault({
228
+ IMPORT_NAME: types.identifier(defaultSpecifier.local.name),
229
+ SOURCE: types.stringLiteral(sourceRelative(defaultResult.source))
230
+ })
231
+ ]
232
+ : defaultSpecifier
233
+ ? [
234
+ buildDefault({
235
+ IMPORT_NAME: types.identifier(defaultSpecifier.local.name),
236
+ SOURCE: types.stringLiteral(source.value)
237
+ })
238
+ ]
239
+ : [];
240
+ const namespaceImport = namespaceSpecifier
241
+ ? [
242
+ buildNamespace({
243
+ IMPORT_NAME: types.identifier(namespaceSpecifier.local.name),
244
+ SOURCE: types.stringLiteral(source.value)
245
+ })
246
+ ]
247
+ : [];
248
+ const named = results.map(({ type, identifier, local, source }) => {
249
+ if (type === 'default') {
250
+ return buildDefault({
251
+ IMPORT_NAME: types.identifier(identifier),
252
+ SOURCE: types.stringLiteral(source)
253
+ });
254
+ }
255
+ else if (identifier !== local) {
256
+ return buildNamedWithAlias({
257
+ IMPORTED_NAME: types.identifier(identifier),
258
+ LOCAL_NAME: types.identifier(local),
259
+ SOURCE: types.stringLiteral(source)
260
+ });
261
+ }
262
+ else {
263
+ return buildNamed({
264
+ IMPORT_NAME: types.identifier(identifier),
265
+ SOURCE: types.stringLiteral(source)
266
+ });
267
+ }
268
+ });
269
+ const newImports = [...namespaceImport, ...defaultImport, ...named].map((node) => {
270
+ node[SKIP] = true;
271
+ return node;
272
+ });
273
+ path.replaceWithMultiple(newImports);
274
+ }
275
+ }
276
+ };
277
+ };
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ /*eslint-disable @typescript-eslint/no-var-requires */
3
+ const node_path = require('path');
4
+ const fs = require('fs');
5
+ const parser = require('@babel/parser');
6
+ const template = require('@babel/template').default;
7
+ const SKIP = Symbol('SKIP');
8
+ module.exports = function plugin({ types }, { tsConfigPath }) {
9
+ const tsConfig = require(tsConfigPath);
10
+ const aliases = tsConfig.compilerOptions.paths;
11
+ const aliasesKeys = Object.keys(aliases);
12
+ const aliasesRegexes = Object.keys(aliases).map((alias) => {
13
+ return new RegExp(`^${alias.replace('*', '(.)+')}$`);
14
+ });
15
+ const cache = new Map();
16
+ const getFile = (original, paths) => {
17
+ if (paths.length === 0) {
18
+ throw new Error('Cannot resolve import ' + original);
19
+ }
20
+ const path = paths[0];
21
+ try {
22
+ return [path, fs.readFileSync(path).toString()];
23
+ }
24
+ catch (e) {
25
+ return getFile(original, paths.slice(1));
26
+ }
27
+ };
28
+ const isPathRelativeOrAliased = (path) => {
29
+ const aliasRegexIdx = aliasesRegexes.findIndex((aliasRegex) => aliasRegex.test(path));
30
+ const isRelative = path.startsWith('./') || path.startsWith('../');
31
+ return aliasRegexIdx > -1 || isRelative;
32
+ };
33
+ const cacheKey = (identifier, filePath) => `${identifier}-${filePath}`;
34
+ const lookup = (identifier, filePath, cwd) => {
35
+ const cached = cache.get(cacheKey(identifier, filePath));
36
+ if (cached) {
37
+ return cached;
38
+ }
39
+ const withExtension = /(\.ts|\.tsx)$/.test(filePath)
40
+ ? [filePath]
41
+ : [
42
+ `${filePath}.ts`,
43
+ `${filePath}.tsx`,
44
+ `${filePath}/index.ts`,
45
+ `${filePath}/index.tsx`,
46
+ `${filePath}.js`,
47
+ `${filePath}.jsx`,
48
+ `${filePath}/index.js`,
49
+ `${filePath}/index.jsx`
50
+ ];
51
+ const [resolvedFilePath, file] = getFile(filePath, withExtension);
52
+ const ast = parser.parse(file, {
53
+ sourceType: 'module',
54
+ plugins: [
55
+ 'jsx',
56
+ 'typescript',
57
+ 'objectRestSpread',
58
+ 'classProperties',
59
+ 'asyncGenerators',
60
+ 'decorators-legacy'
61
+ ]
62
+ });
63
+ /**
64
+ * {
65
+ * identifier?: string,
66
+ * source: string
67
+ * }
68
+ */
69
+ const toLookup = [];
70
+ let resolvedAs = null;
71
+ ast.program.body.forEach((declaration) => {
72
+ var _a;
73
+ if (resolvedAs === null) {
74
+ if (types.isExportNamedDeclaration(declaration)) {
75
+ if (types.isVariableDeclaration(declaration.declaration)) {
76
+ const hasIdentifier = declaration.declaration.declarations.find((declarator) => {
77
+ return declarator.id.name === identifier;
78
+ });
79
+ if (hasIdentifier) {
80
+ resolvedAs = {
81
+ type: 'named',
82
+ identifier,
83
+ source: filePath
84
+ };
85
+ }
86
+ }
87
+ else if (types.isFunctionDeclaration(declaration.declaration) ||
88
+ types.isClassDeclaration(declaration.declaration)) {
89
+ if (declaration.declaration.id.name === identifier) {
90
+ resolvedAs = {
91
+ type: 'named',
92
+ identifier,
93
+ source: filePath
94
+ };
95
+ }
96
+ }
97
+ else {
98
+ const source = (_a = declaration.source) === null || _a === void 0 ? void 0 : _a.value;
99
+ declaration.specifiers.forEach((specifier) => {
100
+ if (types.isExportSpecifier(specifier)) {
101
+ if (specifier.exported.name === identifier) {
102
+ if (specifier.local.name === 'default' && source) {
103
+ resolvedAs = {
104
+ type: 'default',
105
+ identifier,
106
+ source: getModulePath(source, resolvedFilePath, cwd)
107
+ };
108
+ }
109
+ else if (source === undefined) {
110
+ resolvedAs = {
111
+ type: 'named',
112
+ identifier,
113
+ source: filePath
114
+ };
115
+ }
116
+ else if (isPathRelativeOrAliased(source)) {
117
+ toLookup.push({
118
+ identifier: specifier.exported.local,
119
+ source: getModulePath(source, resolvedFilePath, cwd)
120
+ });
121
+ }
122
+ }
123
+ }
124
+ });
125
+ }
126
+ }
127
+ else if (types.isExportAllDeclaration(declaration) &&
128
+ isPathRelativeOrAliased(declaration.source.value)) {
129
+ toLookup.push({
130
+ identifier,
131
+ source: getModulePath(declaration.source.value, resolvedFilePath, cwd)
132
+ });
133
+ }
134
+ }
135
+ });
136
+ if (resolvedAs) {
137
+ return resolvedAs;
138
+ }
139
+ const nestedResult = toLookup
140
+ .map(({ identifier, source }) => lookup(identifier, source, cwd))
141
+ .filter(Boolean);
142
+ return nestedResult[0];
143
+ };
144
+ const getModulePath = (sourcePath, fileName, cwd) => {
145
+ var _a;
146
+ const aliasRegexIdx = aliasesRegexes.findIndex((aliasRegex) => aliasRegex.test(sourcePath));
147
+ const relativeFileName = node_path.relative(cwd, fileName);
148
+ const aliasKey = aliasesKeys[aliasRegexIdx];
149
+ const alias = (_a = aliases[aliasKey]) === null || _a === void 0 ? void 0 : _a[0];
150
+ let modulePath = '';
151
+ if (alias) {
152
+ let relative = alias;
153
+ if (aliasKey.endsWith('*')) {
154
+ const aliasKeyPrefix = aliasKey.replace('*', '');
155
+ relative = alias.replace('*', sourcePath.replace(aliasKeyPrefix, ''));
156
+ }
157
+ modulePath = node_path.resolve(cwd, relative);
158
+ }
159
+ else {
160
+ // we need ../ to skip current file name
161
+ modulePath = node_path.resolve(relativeFileName, '../' + sourcePath);
162
+ }
163
+ return modulePath;
164
+ };
165
+ return {
166
+ visitor: {
167
+ Program: {
168
+ enter(_, { filename }) {
169
+ //console.log(filename);
170
+ }
171
+ },
172
+ ImportDeclaration(path, { filename, cwd }) {
173
+ const sourceRelative = (source) => {
174
+ const rel = node_path.relative(node_path.dirname(filename), source);
175
+ return rel.startsWith('.') ? rel : './' + rel;
176
+ };
177
+ const node = path.node;
178
+ const source = node.source;
179
+ if (source.type !== 'StringLiteral') {
180
+ return;
181
+ }
182
+ const shouldSkip = node[SKIP] || !isPathRelativeOrAliased(source.value);
183
+ if (shouldSkip) {
184
+ return;
185
+ }
186
+ const modulePath = getModulePath(source.value, filename, cwd);
187
+ const defaultSpecifier = node.specifiers.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
188
+ const namespaceSpecifier = node.specifiers.find((specifier) => specifier.type === 'ImportNamespaceSpecifier');
189
+ const specifiers = node.specifiers.filter((specifier) => specifier.type === 'ImportSpecifier');
190
+ const results = specifiers.map((specifier) => {
191
+ const importedName = specifier.imported.name;
192
+ const result = lookup(importedName, modulePath, cwd);
193
+ if (!result) {
194
+ return {
195
+ identifier: importedName,
196
+ local: specifier.local.name,
197
+ source: source.value
198
+ };
199
+ }
200
+ cache.set(cacheKey(importedName, modulePath), result);
201
+ return {
202
+ ...result,
203
+ source: sourceRelative(result.source),
204
+ local: specifier.local.name
205
+ };
206
+ });
207
+ const defaultResult = defaultSpecifier
208
+ ? lookup('default', modulePath, cwd)
209
+ : null;
210
+ if (defaultResult) {
211
+ cache.set(cacheKey('default', modulePath), defaultResult);
212
+ }
213
+ const buildNamed = template(`
214
+ import { %%IMPORT_NAME%% } from %%SOURCE%%;
215
+ `);
216
+ const buildNamedWithAlias = template(`
217
+ import { %%IMPORTED_NAME%% as %%LOCAL_NAME%% } from %%SOURCE%%;
218
+ `);
219
+ const buildDefault = template(`
220
+ import %%IMPORT_NAME%% from %%SOURCE%%;
221
+ `);
222
+ const buildNamespace = template(`
223
+ import * as %%IMPORT_NAME%% from %%SOURCE%%;
224
+ `);
225
+ const defaultImport = defaultResult
226
+ ? [
227
+ buildDefault({
228
+ IMPORT_NAME: types.identifier(defaultSpecifier.local.name),
229
+ SOURCE: types.stringLiteral(sourceRelative(defaultResult.source))
230
+ })
231
+ ]
232
+ : defaultSpecifier
233
+ ? [
234
+ buildDefault({
235
+ IMPORT_NAME: types.identifier(defaultSpecifier.local.name),
236
+ SOURCE: types.stringLiteral(source.value)
237
+ })
238
+ ]
239
+ : [];
240
+ const namespaceImport = namespaceSpecifier
241
+ ? [
242
+ buildNamespace({
243
+ IMPORT_NAME: types.identifier(namespaceSpecifier.local.name),
244
+ SOURCE: types.stringLiteral(source.value)
245
+ })
246
+ ]
247
+ : [];
248
+ const named = results.map(({ type, identifier, local, source }) => {
249
+ if (type === 'default') {
250
+ return buildDefault({
251
+ IMPORT_NAME: types.identifier(identifier),
252
+ SOURCE: types.stringLiteral(source)
253
+ });
254
+ }
255
+ else if (identifier !== local) {
256
+ return buildNamedWithAlias({
257
+ IMPORTED_NAME: types.identifier(identifier),
258
+ LOCAL_NAME: types.identifier(local),
259
+ SOURCE: types.stringLiteral(source)
260
+ });
261
+ }
262
+ else {
263
+ return buildNamed({
264
+ IMPORT_NAME: types.identifier(identifier),
265
+ SOURCE: types.stringLiteral(source)
266
+ });
267
+ }
268
+ });
269
+ const newImports = [...namespaceImport, ...defaultImport, ...named].map((node) => {
270
+ node[SKIP] = true;
271
+ return node;
272
+ });
273
+ path.replaceWithMultiple(newImports);
274
+ }
275
+ }
276
+ };
277
+ };
package/dist/lib/utils.js CHANGED
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.resolvePath = exports.sanitizeUserEntryPoints = exports.asyncFilter = exports.createResolveAbsolutePath = exports.removeInitialDot = void 0;
6
+ exports.findTsConfig = exports.resolvePath = exports.sanitizeUserEntryPoints = exports.asyncFilter = exports.createResolveAbsolutePath = exports.removeInitialDot = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
8
9
  const glob_escape_1 = __importDefault(require("glob-escape"));
9
10
  const removeInitialDot = (path) => path.replace(/^\.\//, '');
10
11
  exports.removeInitialDot = removeInitialDot;
@@ -27,3 +28,17 @@ const resolvePath = (p) => {
27
28
  return path_1.default.resolve(p);
28
29
  };
29
30
  exports.resolvePath = resolvePath;
31
+ const findTsConfig = (cwd = process.cwd()) => {
32
+ try {
33
+ const tsconfig = path_1.default.join(cwd, 'tsconfig.json');
34
+ const stat = fs_1.default.statSync(tsconfig);
35
+ if (stat.isFile()) {
36
+ return tsconfig;
37
+ }
38
+ }
39
+ catch {
40
+ /**/
41
+ }
42
+ return undefined;
43
+ };
44
+ exports.findTsConfig = findTsConfig;
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "rev-dep",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "Reverse dependency resolution tool built with dependency-cruiser",
5
5
  "main": "lib/find.js",
6
6
  "bin": "bin.js",
7
7
  "files": [
8
8
  "dist/**",
9
9
  "lib/**",
10
- "bin.js"
10
+ "bin.js",
11
+ "babel.js"
11
12
  ],
12
13
  "author": "Jakub Mazurek @jayu",
13
14
  "license": "MIT",
@@ -31,6 +32,8 @@
31
32
  "typecheck": "tsc --noEmit"
32
33
  },
33
34
  "dependencies": {
35
+ "@babel/parser": "^7.17.8",
36
+ "@babel/template": "^7.16.7",
34
37
  "@types/dedent": "^0.7.0",
35
38
  "colorette": "^2.0.16",
36
39
  "commander": "^6.1.0",
@@ -43,6 +46,7 @@
43
46
  "minimatch": "^5.0.1"
44
47
  },
45
48
  "devDependencies": {
49
+ "@babel/types": "^7.17.0",
46
50
  "@typescript-eslint/eslint-plugin": "^5.16.0",
47
51
  "@typescript-eslint/parser": "^5.16.0",
48
52
  "eslint": "^7.11.0",