@clipboard-health/oxlint-plugin 1.0.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 +27 -0
- package/package.json +30 -0
- package/src/index.d.ts +9 -0
- package/src/index.js +21 -0
- package/src/index.js.map +1 -0
- package/src/lib/internal/findVariable.d.ts +4 -0
- package/src/lib/internal/findVariable.js +15 -0
- package/src/lib/internal/findVariable.js.map +1 -0
- package/src/lib/rules/enforce-ts-rest-in-controllers/index.d.ts +2 -0
- package/src/lib/rules/enforce-ts-rest-in-controllers/index.js +111 -0
- package/src/lib/rules/enforce-ts-rest-in-controllers/index.js.map +1 -0
- package/src/lib/rules/no-cross-contract-imports/index.d.ts +2 -0
- package/src/lib/rules/no-cross-contract-imports/index.js +116 -0
- package/src/lib/rules/no-cross-contract-imports/index.js.map +1 -0
- package/src/lib/rules/require-contract-fixture-construction/index.d.ts +3 -0
- package/src/lib/rules/require-contract-fixture-construction/index.js +615 -0
- package/src/lib/rules/require-contract-fixture-construction/index.js.map +1 -0
- package/src/lib/rules/require-contract-response-parse/index.d.ts +21 -0
- package/src/lib/rules/require-contract-response-parse/index.js +368 -0
- package/src/lib/rules/require-contract-response-parse/index.js.map +1 -0
- package/src/lib/rules/require-http-module-factory/index.d.ts +2 -0
- package/src/lib/rules/require-http-module-factory/index.js +74 -0
- package/src/lib/rules/require-http-module-factory/index.js.map +1 -0
- package/src/lib/rules/require-run-validators-with-upsert/index.d.ts +2 -0
- package/src/lib/rules/require-run-validators-with-upsert/index.js +110 -0
- package/src/lib/rules/require-run-validators-with-upsert/index.js.map +1 -0
- package/src/lib/rules/require-zod-import-in-contracts/index.d.ts +2 -0
- package/src/lib/rules/require-zod-import-in-contracts/index.js +46 -0
- package/src/lib/rules/require-zod-import-in-contracts/index.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @clipboard-health/oxlint-plugin
|
|
2
|
+
|
|
3
|
+
Clipboard's custom Oxlint rules. Consumers should normally use the presets exported by
|
|
4
|
+
`@clipboard-health/oxlint-config`.
|
|
5
|
+
|
|
6
|
+
The package is independent of ESLint and `@typescript-eslint`. Its standard `create(context)`
|
|
7
|
+
rules are also re-exported by `@clipboard-health/eslint-plugin` for repositories that still use
|
|
8
|
+
ESLint.
|
|
9
|
+
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- `enforce-ts-rest-in-controllers`
|
|
13
|
+
- `no-cross-contract-imports`
|
|
14
|
+
- `require-contract-fixture-construction`
|
|
15
|
+
- `require-contract-response-parse`
|
|
16
|
+
- `require-http-module-factory`
|
|
17
|
+
- `require-run-validators-with-upsert`
|
|
18
|
+
- `require-zod-import-in-contracts`
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Use the `customRules` preset from `@clipboard-health/oxlint-config` to register the plugin and
|
|
23
|
+
apply the shared controller, module, contract, and cross-contract-import scopes. The
|
|
24
|
+
`contractFixtures` preset enables fixture-construction checks in tests.
|
|
25
|
+
|
|
26
|
+
`require-contract-response-parse` and `require-run-validators-with-upsert` are opt-in because their
|
|
27
|
+
safe adoption scope is repository-specific.
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clipboard-health/oxlint-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Clipboard's Oxlint plugin.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"oxlint",
|
|
7
|
+
"plugin"
|
|
8
|
+
],
|
|
9
|
+
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/ClipboardHealth/core-utils.git",
|
|
14
|
+
"directory": "packages/oxlint-plugin"
|
|
15
|
+
},
|
|
16
|
+
"type": "commonjs",
|
|
17
|
+
"main": "./src/index.js",
|
|
18
|
+
"typings": "./src/index.d.ts",
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@oxlint/plugins": "1.77.0",
|
|
24
|
+
"tslib": "2.8.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"oxlint": "1.77.0"
|
|
28
|
+
},
|
|
29
|
+
"types": "./src/index.d.ts"
|
|
30
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const rules: {
|
|
2
|
+
"enforce-ts-rest-in-controllers": import("@oxlint/plugins").Rule;
|
|
3
|
+
"no-cross-contract-imports": import("@oxlint/plugins").Rule;
|
|
4
|
+
"require-contract-fixture-construction": import("@oxlint/plugins").Rule;
|
|
5
|
+
"require-contract-response-parse": import("@oxlint/plugins").Rule;
|
|
6
|
+
"require-http-module-factory": import("@oxlint/plugins").Rule;
|
|
7
|
+
"require-run-validators-with-upsert": import("@oxlint/plugins").Rule;
|
|
8
|
+
"require-zod-import-in-contracts": import("@oxlint/plugins").Rule;
|
|
9
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rules = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const enforce_ts_rest_in_controllers_1 = tslib_1.__importDefault(require("./lib/rules/enforce-ts-rest-in-controllers"));
|
|
6
|
+
const no_cross_contract_imports_1 = tslib_1.__importDefault(require("./lib/rules/no-cross-contract-imports"));
|
|
7
|
+
const require_contract_fixture_construction_1 = tslib_1.__importDefault(require("./lib/rules/require-contract-fixture-construction"));
|
|
8
|
+
const require_contract_response_parse_1 = tslib_1.__importDefault(require("./lib/rules/require-contract-response-parse"));
|
|
9
|
+
const require_http_module_factory_1 = tslib_1.__importDefault(require("./lib/rules/require-http-module-factory"));
|
|
10
|
+
const require_run_validators_with_upsert_1 = tslib_1.__importDefault(require("./lib/rules/require-run-validators-with-upsert"));
|
|
11
|
+
const require_zod_import_in_contracts_1 = tslib_1.__importDefault(require("./lib/rules/require-zod-import-in-contracts"));
|
|
12
|
+
exports.rules = {
|
|
13
|
+
"enforce-ts-rest-in-controllers": enforce_ts_rest_in_controllers_1.default,
|
|
14
|
+
"no-cross-contract-imports": no_cross_contract_imports_1.default,
|
|
15
|
+
"require-contract-fixture-construction": require_contract_fixture_construction_1.default,
|
|
16
|
+
"require-contract-response-parse": require_contract_response_parse_1.default,
|
|
17
|
+
"require-http-module-factory": require_http_module_factory_1.default,
|
|
18
|
+
"require-run-validators-with-upsert": require_run_validators_with_upsert_1.default,
|
|
19
|
+
"require-zod-import-in-contracts": require_zod_import_in_contracts_1.default,
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/oxlint-plugin/src/index.ts"],"names":[],"mappings":";;;;AAAA,wHAAoF;AACpF,8GAA2E;AAC3E,sIAAmG;AACnG,0HAAuF;AACvF,kHAA+E;AAC/E,gIAA4F;AAC5F,0HAAsF;AAEzE,QAAA,KAAK,GAAG;IACnB,gCAAgC,EAAE,wCAA0B;IAC5D,2BAA2B,EAAE,mCAAsB;IACnD,uCAAuC,EAAE,+CAAkC;IAC3E,iCAAiC,EAAE,yCAA4B;IAC/D,6BAA6B,EAAE,qCAAwB;IACvD,oCAAoC,EAAE,4CAA8B;IACpE,iCAAiC,EAAE,yCAA2B;CAC/D,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ESTree, SourceCode, Variable } from "@oxlint/plugins";
|
|
2
|
+
type Identifier = ESTree.BindingIdentifier | ESTree.IdentifierName | ESTree.IdentifierReference;
|
|
3
|
+
export declare function findVariable(sourceCode: SourceCode, identifier: Identifier): Variable | undefined;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findVariable = findVariable;
|
|
4
|
+
function findVariable(sourceCode, identifier) {
|
|
5
|
+
let scope = sourceCode.getScope(identifier);
|
|
6
|
+
while (scope !== null) {
|
|
7
|
+
const variable = scope.set.get(identifier.name);
|
|
8
|
+
if (variable !== undefined) {
|
|
9
|
+
return variable;
|
|
10
|
+
}
|
|
11
|
+
scope = scope.upper;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=findVariable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findVariable.js","sourceRoot":"","sources":["../../../../../../packages/oxlint-plugin/src/lib/internal/findVariable.ts"],"names":[],"mappings":";;;AAIA,sBAA6B,UAAsB,EAAE,UAAsB;IACzE,IAAI,KAAK,GAAiB,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE1D,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const plugins_1 = require("@oxlint/plugins");
|
|
4
|
+
function validateDecorators(context, node, symbolName, decoratorImportedCorrectly) {
|
|
5
|
+
const decorators = node.decorators ?? [];
|
|
6
|
+
const hasMatchingDecorator = decorators.some((decorator) => decorator.expression.type === "CallExpression" &&
|
|
7
|
+
decorator.expression.callee.type === "Identifier" &&
|
|
8
|
+
decorator.expression.callee.name === "TsRestHandler");
|
|
9
|
+
if (!hasMatchingDecorator) {
|
|
10
|
+
context.report({
|
|
11
|
+
node,
|
|
12
|
+
messageId: "missingDecorator",
|
|
13
|
+
data: { name: symbolName },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
for (const decorator of decorators) {
|
|
17
|
+
if (decorator.expression.type === "CallExpression" &&
|
|
18
|
+
decorator.expression.callee.type === "Identifier" &&
|
|
19
|
+
decorator.expression.callee.name === "TsRestHandler" &&
|
|
20
|
+
!decoratorImportedCorrectly) {
|
|
21
|
+
context.report({
|
|
22
|
+
node: decorator,
|
|
23
|
+
messageId: "decoratorNotFromPackage",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function validateReturnStatement(context, node, symbolName, methodImportedCorrectly) {
|
|
29
|
+
const body = node.value.body;
|
|
30
|
+
if (body?.type !== "BlockStatement" ||
|
|
31
|
+
body.body.length !== 1 ||
|
|
32
|
+
body.body[0]?.type !== "ReturnStatement") {
|
|
33
|
+
context.report({
|
|
34
|
+
node,
|
|
35
|
+
messageId: "missingReturn",
|
|
36
|
+
data: { name: symbolName },
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const returnValueExpression = body.body[0].argument;
|
|
41
|
+
if (returnValueExpression?.type !== "CallExpression" ||
|
|
42
|
+
returnValueExpression.callee.type !== "Identifier" ||
|
|
43
|
+
returnValueExpression.callee.name !== "tsRestHandler") {
|
|
44
|
+
context.report({
|
|
45
|
+
node,
|
|
46
|
+
messageId: "missingReturn",
|
|
47
|
+
data: { name: symbolName },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (returnValueExpression?.type === "CallExpression" &&
|
|
51
|
+
returnValueExpression.callee.type === "Identifier" &&
|
|
52
|
+
returnValueExpression.callee.name === "tsRestHandler" &&
|
|
53
|
+
!methodImportedCorrectly) {
|
|
54
|
+
context.report({
|
|
55
|
+
node: returnValueExpression,
|
|
56
|
+
messageId: "callNotFromPackage",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const rule = (0, plugins_1.defineRule)({
|
|
61
|
+
meta: {
|
|
62
|
+
type: "problem",
|
|
63
|
+
docs: {
|
|
64
|
+
description: "Require use of ts-rest on all controller methods",
|
|
65
|
+
url: "https://github.com/ClipboardHealth/core-utils/tree/main/packages/eslint-plugin/src/lib/rules/enforce-ts-rest-in-controllers",
|
|
66
|
+
},
|
|
67
|
+
schema: [],
|
|
68
|
+
messages: {
|
|
69
|
+
missingDecorator: "Controller method '{{name}}' must be decorated with `@TsRestHandler()` from `@ts-rest/nest` package. See https://www.notion.so/BP-REST-API-f769b7fe745c4cf38f6eca2e9ad8a843 for more information.",
|
|
70
|
+
missingReturn: "Controller method '{{name}}' must only return the result of calling `tsRestHandler()` from `@ts-rest/nest` package. See https://www.notion.so/BP-REST-API-f769b7fe745c4cf38f6eca2e9ad8a843 for more information.",
|
|
71
|
+
decoratorNotFromPackage: "Decorator `TsRestHandler` must be imported from `@ts-rest/nest`",
|
|
72
|
+
callNotFromPackage: "Method `tsRestHandler` must be imported from `@ts-rest/nest`",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
create(context) {
|
|
76
|
+
let methodImportedCorrectly = false;
|
|
77
|
+
let decoratorImportedCorrectly = false;
|
|
78
|
+
return {
|
|
79
|
+
ImportDeclaration(node) {
|
|
80
|
+
if (node.source.value !== "@ts-rest/nest") {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const specifier of node.specifiers) {
|
|
84
|
+
if (specifier.type !== "ImportSpecifier") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (specifier.imported.type === "Identifier" &&
|
|
88
|
+
specifier.imported.name === "tsRestHandler") {
|
|
89
|
+
methodImportedCorrectly = true;
|
|
90
|
+
}
|
|
91
|
+
if (specifier.imported.type === "Identifier" &&
|
|
92
|
+
specifier.imported.name === "TsRestHandler") {
|
|
93
|
+
decoratorImportedCorrectly = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
MethodDefinition(node) {
|
|
98
|
+
const symbolName = node.key.type === "Identifier" ? node.key.name : "<unknown>";
|
|
99
|
+
if (node.kind === "constructor" ||
|
|
100
|
+
node.accessibility === "private" ||
|
|
101
|
+
(node.key.type === "Identifier" && node.key.name === "constructor")) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
validateDecorators(context, node, symbolName, decoratorImportedCorrectly);
|
|
105
|
+
validateReturnStatement(context, node, symbolName, methodImportedCorrectly);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
exports.default = rule;
|
|
111
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../packages/oxlint-plugin/src/lib/rules/enforce-ts-rest-in-controllers/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Forbid contract packages from importing other contract packages.
|
|
6
|
+
* Cross-contract dependencies pin one contract into another's dependencies,
|
|
7
|
+
* vendoring duplicate copies into every consumer and risking dependency cycles.
|
|
8
|
+
*/
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
11
|
+
const plugins_1 = require("@oxlint/plugins");
|
|
12
|
+
const CONTRACT_PACKAGE_PATTERN = /^@clipboard-health\/(?:contract-|api-contract-|flag-)/;
|
|
13
|
+
const SHARED_CONTRACT_PACKAGE = "@clipboard-health/contract-core";
|
|
14
|
+
const packageNameCache = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Walks up from `directory` to the nearest package.json that declares a `name`.
|
|
17
|
+
* Skips nameless manifests (e.g. `{"type":"module"}` build-output markers).
|
|
18
|
+
*/
|
|
19
|
+
function nearestPackageName(directory) {
|
|
20
|
+
if (packageNameCache.has(directory)) {
|
|
21
|
+
return packageNameCache.get(directory);
|
|
22
|
+
}
|
|
23
|
+
let name;
|
|
24
|
+
const manifestPath = node_path_1.default.join(directory, "package.json");
|
|
25
|
+
try {
|
|
26
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- resolves the linted file's own package.json
|
|
27
|
+
const manifest = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, "utf8"));
|
|
28
|
+
if (typeof manifest === "object" &&
|
|
29
|
+
manifest !== null &&
|
|
30
|
+
"name" in manifest &&
|
|
31
|
+
typeof manifest.name === "string") {
|
|
32
|
+
name = manifest.name;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// No package.json in this directory; keep walking up.
|
|
37
|
+
}
|
|
38
|
+
if (name === undefined) {
|
|
39
|
+
const parent = node_path_1.default.dirname(directory);
|
|
40
|
+
name = parent === directory ? undefined : nearestPackageName(parent);
|
|
41
|
+
}
|
|
42
|
+
packageNameCache.set(directory, name);
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
function isBannedSpecifier(specifier) {
|
|
46
|
+
return (CONTRACT_PACKAGE_PATTERN.test(specifier) &&
|
|
47
|
+
specifier !== SHARED_CONTRACT_PACKAGE &&
|
|
48
|
+
!specifier.startsWith(`${SHARED_CONTRACT_PACKAGE}/`));
|
|
49
|
+
}
|
|
50
|
+
const rule = (0, plugins_1.defineRule)({
|
|
51
|
+
meta: {
|
|
52
|
+
type: "problem",
|
|
53
|
+
docs: {
|
|
54
|
+
description: "Forbid contract packages from importing other contract packages; contracts own the shape of their inputs and outputs",
|
|
55
|
+
url: "https://github.com/ClipboardHealth/core-utils/tree/main/packages/eslint-plugin/src/lib/rules/no-cross-contract-imports",
|
|
56
|
+
},
|
|
57
|
+
schema: [],
|
|
58
|
+
messages: {
|
|
59
|
+
noCrossContractImport: "Contract packages must not depend on '{{specifier}}'. Cross-contract imports (including type-only imports and re-exports) pin another contract into this package's dependencies, vendoring duplicate copies into every consumer and risking dependency cycles. Import shared primitives from @clipboard-health/contract-core, or duplicate the schema locally — type-checking and response validation catch drift.",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
create(context) {
|
|
63
|
+
// `filename` requires ESLint >=8.40; keep the fallback for older ESLint 8 hosts.
|
|
64
|
+
const filename = context.filename || context.getFilename();
|
|
65
|
+
const packageName = nearestPackageName(node_path_1.default.dirname(filename));
|
|
66
|
+
if (packageName === undefined || !CONTRACT_PACKAGE_PATTERN.test(packageName)) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
function checkSpecifier(node, specifier) {
|
|
70
|
+
const isSelfImport = specifier === packageName || specifier.startsWith(`${packageName}/`);
|
|
71
|
+
if (!isSelfImport && isBannedSpecifier(specifier)) {
|
|
72
|
+
context.report({
|
|
73
|
+
node,
|
|
74
|
+
messageId: "noCrossContractImport",
|
|
75
|
+
data: { specifier },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
ImportDeclaration(node) {
|
|
81
|
+
checkSpecifier(node, node.source.value);
|
|
82
|
+
},
|
|
83
|
+
ExportNamedDeclaration(node) {
|
|
84
|
+
if (node.source) {
|
|
85
|
+
checkSpecifier(node, node.source.value);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
ExportAllDeclaration(node) {
|
|
89
|
+
checkSpecifier(node, node.source.value);
|
|
90
|
+
},
|
|
91
|
+
ImportExpression(node) {
|
|
92
|
+
if (node.source.type === "Literal" && typeof node.source.value === "string") {
|
|
93
|
+
checkSpecifier(node, node.source.value);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
CallExpression(node) {
|
|
97
|
+
const [argument] = node.arguments;
|
|
98
|
+
if (node.callee.type === "Identifier" &&
|
|
99
|
+
node.callee.name === "require" &&
|
|
100
|
+
argument?.type === "Literal" &&
|
|
101
|
+
typeof argument.value === "string") {
|
|
102
|
+
checkSpecifier(node, argument.value);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
TSImportEqualsDeclaration(node) {
|
|
106
|
+
if (node.moduleReference.type === "TSExternalModuleReference" &&
|
|
107
|
+
node.moduleReference.expression.type === "Literal" &&
|
|
108
|
+
typeof node.moduleReference.expression.value === "string") {
|
|
109
|
+
checkSpecifier(node, node.moduleReference.expression.value);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
exports.default = rule;
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../packages/oxlint-plugin/src/lib/rules/no-cross-contract-imports/index.ts"],"names":[],"mappings":"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"}
|