Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'

This commit is contained in:
-Puter
2026-07-19 03:25:10 +05:30
parent dd1071cc10
commit 2daf979036
2214 changed files with 673090 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
import rule from "@effect/oxc/oxlint/rules/jsdocs"
import * as crypto from "node:crypto"
import * as fs from "node:fs"
import * as os from "node:os"
import * as path from "node:path"
import { describe, expect, it } from "vitest"
import { createTestContext } from "./utils.ts"
function hash(source: string): string {
return crypto.createHash("sha256").update(source).digest("hex")
}
function run(source: string, cwd: string, filename: string, model = ".data/jsdocs.json") {
const { context, errors } = createTestContext({ sourceCode: source, cwd, filename, ruleOptions: [{ model }] })
const visitors = rule.create(context as never)
visitors.Program?.({ type: "Program", range: [0, source.length] } as never)
return errors
}
describe("jsdocs", () => {
it("skips when the model is missing", () => {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-"))
const errors = run("export const a = 1\n", cwd, path.join(cwd, "src/Foo.ts"))
expect(errors).toEqual([])
})
it("reports invalid model files", () => {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-"))
fs.mkdirSync(path.join(cwd, ".data"), { recursive: true })
fs.writeFileSync(path.join(cwd, ".data/jsdocs.json"), "{")
const errors = run("export const a = 1\n", cwd, path.join(cwd, "src/Foo.ts"))
expect(errors.map((error) => error.message)).toEqual([expect.stringContaining("Invalid jsdocs model")])
})
it("reports stale model files", () => {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-"))
fs.mkdirSync(path.join(cwd, ".data"), { recursive: true })
fs.writeFileSync(
path.join(cwd, ".data/jsdocs.json"),
JSON.stringify({
version: 2,
generatedBy: "@effect/jsdocs",
generatedAt: "now",
files: [{ file: "src/Foo.ts", hash: "old", diagnostics: [], declarations: [], namespaces: [] }],
apis: []
})
)
const errors = run("export const a = 1\n", cwd, path.join(cwd, "src/Foo.ts"))
expect(errors.map((error) => error.message)).toEqual(["JSDoc model is stale for this file; run `pnpm jsdocs`"])
})
it("reports model diagnostics", () => {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-"))
const source = "export const a = 1\n"
fs.mkdirSync(path.join(cwd, ".data"), { recursive: true })
fs.writeFileSync(
path.join(cwd, ".data/jsdocs.json"),
JSON.stringify({
version: 2,
generatedBy: "@effect/jsdocs",
generatedAt: "now",
apis: [],
files: [{
file: "src/Foo.ts",
hash: hash(source),
diagnostics: [{ code: "missing-jsdoc", message: "Public JSDoc is required", range: [0, 6] }],
declarations: [],
namespaces: []
}]
})
)
const errors = run(source, cwd, path.join(cwd, "src/Foo.ts"))
expect(errors.map((error) => error.message)).toEqual(["Public JSDoc is required"])
})
})

View File

@@ -0,0 +1,182 @@
import rule from "@effect/oxc/oxlint/rules/no-import-from-barrel-package"
import { describe, expect, it } from "vitest"
import { runRule } from "./utils.ts"
describe("no-import-from-barrel-package", () => {
const testOptions = {
filename: "/test/file.ts",
cwd: "/test",
ruleOptions: [{ checkPatterns: ["^effect$"] }]
}
const createImportDeclaration = (
source: string,
specifiers: Array<unknown>,
importKind?: "type" | "value"
) => {
const base = {
type: "ImportDeclaration" as const,
source: { value: source },
specifiers,
range: [0, 50] as [number, number]
}
return importKind !== undefined ? { ...base, importKind } : base
}
const createNamedSpecifier = (
name: string,
local?: string,
importKind?: "type" | "value"
) => {
const base = {
type: "ImportSpecifier" as const,
imported: { type: "Identifier" as const, name },
local: { name: local ?? name }
}
return importKind !== undefined ? { ...base, importKind } : base
}
it("should not report for imports from non-barrel packages", () => {
const node = createImportDeclaration("effect/Effect", [
createNamedSpecifier("Effect")
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(0)
})
it("should report for named imports from effect barrel", () => {
const node = createImportDeclaration("effect", [
createNamedSpecifier("Effect")
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(
`Use import * as Effect from "effect/Effect" instead`
)
})
it("should report for multiple named imports from effect barrel", () => {
const node = createImportDeclaration("effect", [
createNamedSpecifier("Effect"),
createNamedSpecifier("Option"),
createNamedSpecifier("Either")
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(3)
})
it("should not report for type-only import declarations", () => {
const node = createImportDeclaration(
"effect",
[createNamedSpecifier("Effect")],
"type"
)
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(0)
})
it("should not report for type imports within named specifiers", () => {
const node = createImportDeclaration("effect", [
createNamedSpecifier("Effect", "Effect", "type")
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(0)
})
it("should handle aliased imports", () => {
const node = createImportDeclaration("effect", [
createNamedSpecifier("Effect", "Eff")
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(
`Use import * as Eff from "effect/Effect" instead`
)
})
it("should report for namespace imports from barrel", () => {
const node = createImportDeclaration("effect", [
{
type: "ImportNamespaceSpecifier",
local: { name: "Effect" }
}
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(
`Do not use namespace import from barrel file "effect", import from specific modules instead`
)
})
it("should not report for namespace imports from module paths", () => {
const node = createImportDeclaration("effect/Effect", [
{
type: "ImportNamespaceSpecifier",
local: { name: "Effect" }
}
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(0)
})
it("should not report for default imports", () => {
const node = createImportDeclaration("effect", [
{
type: "ImportDefaultSpecifier",
local: { name: "Effect" }
}
])
const errors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(errors).toHaveLength(0)
})
describe("configuration", () => {
it("should match regex patterns", () => {
const node = createImportDeclaration("@myorg/utils", [
createNamedSpecifier("helper")
])
// No patterns configured - doesn't match
const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(defaultErrors).toHaveLength(0)
// Pattern matches @myorg/*
const customErrors = runRule(rule, "ImportDeclaration", node, {
...testOptions,
ruleOptions: [{ checkPatterns: ["^@myorg/"] }]
})
expect(customErrors).toHaveLength(1)
})
it("should match exact package with pattern", () => {
const node = createImportDeclaration("lodash", [
createNamedSpecifier("map")
])
// No patterns - doesn't match
const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(defaultErrors).toHaveLength(0)
// Exact match pattern
const customErrors = runRule(rule, "ImportDeclaration", node, {
...testOptions,
ruleOptions: [{ checkPatterns: ["^lodash$"] }]
})
expect(customErrors).toHaveLength(1)
})
it("should disable relative index checking when configured", () => {
const node = createImportDeclaration("./index.ts", [
createNamedSpecifier("foo")
])
// Default checks relative imports
const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions)
expect(defaultErrors).toHaveLength(1)
// Disabled relative checking
const customErrors = runRule(rule, "ImportDeclaration", node, {
...testOptions,
ruleOptions: [{ checkRelativeIndexImports: false }]
})
expect(customErrors).toHaveLength(0)
})
})
})

View File

@@ -0,0 +1,120 @@
import rule from "@effect/oxc/oxlint/rules/no-js-extension-imports"
import { describe, expect, it } from "vitest"
import { runRule } from "./utils.ts"
describe("no-js-extension-imports", () => {
const createImportDeclaration = (source: string) => ({
type: "ImportDeclaration",
source: { value: source, range: [8, 8 + source.length + 2] as [number, number] },
range: [0, 50] as [number, number]
})
const createExportAllDeclaration = (source: string) => ({
type: "ExportAllDeclaration",
source: { value: source, range: [14, 14 + source.length + 2] as [number, number] },
range: [0, 50] as [number, number]
})
const createExportNamedDeclaration = (source: string) => ({
type: "ExportNamedDeclaration",
source: { value: source, range: [9, 9 + source.length + 2] as [number, number] },
range: [0, 50] as [number, number]
})
describe("ImportDeclaration", () => {
it("should report .js extension in relative imports", () => {
const node = createImportDeclaration("./foo.js")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".ts" extension instead of ".js" for relative imports`)
})
it("should report .jsx extension in relative imports", () => {
const node = createImportDeclaration("./component.jsx")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".tsx" extension instead of ".jsx" for relative imports`)
})
it("should report .mjs extension in relative imports", () => {
const node = createImportDeclaration("../utils.mjs")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".mts" extension instead of ".mjs" for relative imports`)
})
it("should report .cjs extension in relative imports", () => {
const node = createImportDeclaration("./config.cjs")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".cts" extension instead of ".cjs" for relative imports`)
})
it("should not report .ts extension in relative imports", () => {
const node = createImportDeclaration("./foo.ts")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should not report .tsx extension in relative imports", () => {
const node = createImportDeclaration("./component.tsx")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should not report for package imports with .js extension", () => {
const node = createImportDeclaration("some-package/utils.js")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should not report for bare package imports", () => {
const node = createImportDeclaration("effect")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should handle deeply nested relative imports", () => {
const node = createImportDeclaration("../../lib/utils.js")
const errors = runRule(rule, "ImportDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".ts" extension instead of ".js" for relative imports`)
})
})
describe("ExportAllDeclaration", () => {
it("should report .js extension in relative re-exports", () => {
const node = createExportAllDeclaration("./foo.js")
const errors = runRule(rule, "ExportAllDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".ts" extension instead of ".js" for relative imports`)
})
it("should not report .ts extension in relative re-exports", () => {
const node = createExportAllDeclaration("./foo.ts")
const errors = runRule(rule, "ExportAllDeclaration", node)
expect(errors).toHaveLength(0)
})
})
describe("ExportNamedDeclaration", () => {
it("should report .js extension in relative named exports", () => {
const node = createExportNamedDeclaration("./foo.js")
const errors = runRule(rule, "ExportNamedDeclaration", node)
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe(`Use ".ts" extension instead of ".js" for relative imports`)
})
it("should not report .ts extension in relative named exports", () => {
const node = createExportNamedDeclaration("./foo.ts")
const errors = runRule(rule, "ExportNamedDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should handle export without source", () => {
const node = { type: "ExportNamedDeclaration", source: null }
const errors = runRule(rule, "ExportNamedDeclaration", node)
expect(errors).toHaveLength(0)
})
})
})

View File

@@ -0,0 +1,276 @@
import rule from "@effect/oxc/oxlint/rules/no-opaque-instance-fields"
import { describe, expect, it } from "vitest"
import { createTestContext, runRule } from "./utils.ts"
function runRuleWithNodes(nodes: Array<{ visitor: string; node: unknown }>) {
const { context, errors } = createTestContext()
const visitors = rule.create(context as never)
for (const { visitor, node } of nodes) {
const handler = visitors[visitor as keyof typeof visitors]
if (handler) {
;(handler as (node: unknown) => void)(node)
}
}
return errors
}
describe("no-opaque-instance-fields", () => {
const createOpaqueClass = (
callee: { type: string; name?: string; object?: unknown; property?: unknown },
members: Array<{ type: string; static: boolean }>
) => ({
type: "ClassDeclaration",
superClass: {
type: "CallExpression",
callee: {
type: "CallExpression",
callee
}
},
body: {
body: members
}
})
const createSchemaOpaqueClass = (members: Array<{ type: string; static: boolean }>) =>
createOpaqueClass({
type: "MemberExpression",
object: { type: "Identifier", name: "Schema" },
property: { type: "Identifier", name: "Opaque" }
}, members)
const createSchemaImport = () => ({
type: "ImportDeclaration",
source: { type: "Literal", value: "effect" },
specifiers: [{
type: "ImportSpecifier",
imported: { type: "Identifier", name: "Schema" },
local: { type: "Identifier", name: "Schema" },
importKind: "value"
}],
importKind: "value"
})
const createOpaqueImport = (localName = "Opaque") => ({
type: "ImportDeclaration",
source: { type: "Literal", value: "effect/Schema" },
specifiers: [{
type: "ImportSpecifier",
imported: { type: "Identifier", name: "Opaque" },
local: { type: "Identifier", name: localName },
importKind: "value"
}],
importKind: "value"
})
const createSchemaNamespaceImport = (localName = "S") => ({
type: "ImportDeclaration",
source: { type: "Literal", value: "effect/Schema" },
specifiers: [{
type: "ImportNamespaceSpecifier",
local: { type: "Identifier", name: localName }
}],
importKind: "value"
})
const createOtherOpaqueImport = (localName = "Opaque") => ({
type: "ImportDeclaration",
source: { type: "Literal", value: "other" },
specifiers: [{
type: "ImportSpecifier",
imported: { type: "Identifier", name: "Opaque" },
local: { type: "Identifier", name: localName },
importKind: "value"
}],
importKind: "value"
})
const createOtherNamespaceImport = (localName = "S") => ({
type: "ImportDeclaration",
source: { type: "Literal", value: "other" },
specifiers: [{
type: "ImportNamespaceSpecifier",
local: { type: "Identifier", name: localName }
}],
importKind: "value"
})
const createRegularClass = (members: Array<{ type: string; static: boolean }>) => ({
type: "ClassDeclaration",
superClass: {
type: "Identifier",
name: "SomeClass"
},
body: {
body: members
}
})
it("should not report for class without instance fields", () => {
const node = createSchemaOpaqueClass([])
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(0)
})
it("should not report for static fields in Schema.Opaque class", () => {
const node = createSchemaOpaqueClass([
{ type: "PropertyDefinition", static: true }
])
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(0)
})
it("should report for instance fields in Schema.Opaque class", () => {
const node = createSchemaOpaqueClass([
{ type: "PropertyDefinition", static: false }
])
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(1)
expect(errors[0].message).toBe("Classes extending Schema.Opaque must not have instance members")
})
it("should not report when Schema.Opaque has no Schema import", () => {
const node = createSchemaOpaqueClass([
{ type: "PropertyDefinition", static: false }
])
const errors = runRule(rule, "ClassDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should report for instance fields in Opaque class", () => {
const node = createOpaqueClass(
{ type: "Identifier", name: "Opaque" },
[{ type: "PropertyDefinition", static: false }]
)
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createOpaqueImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(1)
})
it("should report for instance fields in aliased Opaque class", () => {
const node = createOpaqueClass(
{
type: "MemberExpression",
object: { type: "Identifier", name: "S" },
property: { type: "Identifier", name: "Opaque" }
},
[{ type: "PropertyDefinition", static: false }]
)
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaNamespaceImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(1)
})
it("should not report for Opaque import from other module", () => {
const node = createOpaqueClass(
{ type: "Identifier", name: "Opaque" },
[{ type: "PropertyDefinition", static: false }]
)
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createOtherOpaqueImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(0)
})
it("should not report for namespace Opaque from other module", () => {
const node = createOpaqueClass(
{
type: "MemberExpression",
object: { type: "Identifier", name: "S" },
property: { type: "Identifier", name: "Opaque" }
},
[{ type: "PropertyDefinition", static: false }]
)
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createOtherNamespaceImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(0)
})
it("should report for multiple instance fields", () => {
const node = createSchemaOpaqueClass([
{ type: "PropertyDefinition", static: false },
{ type: "PropertyDefinition", static: true },
{ type: "PropertyDefinition", static: false }
])
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(2)
})
it("should report for instance methods", () => {
const node = createSchemaOpaqueClass([
{ type: "MethodDefinition", static: false }
])
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaImport() },
{ visitor: "ClassDeclaration", node }
])
expect(errors).toHaveLength(1)
})
it("should not report for non-Schema.Opaque classes", () => {
const node = createRegularClass([
{ type: "PropertyDefinition", static: false }
])
const errors = runRule(rule, "ClassDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should work with ClassExpression", () => {
const node = {
...createSchemaOpaqueClass([
{ type: "PropertyDefinition", static: false }
]),
type: "ClassExpression"
}
const errors = runRuleWithNodes([
{ visitor: "ImportDeclaration", node: createSchemaImport() },
{ visitor: "ClassExpression", node }
])
expect(errors).toHaveLength(1)
})
it("should not report for class without superClass", () => {
const node = {
type: "ClassDeclaration",
body: {
body: [{ type: "PropertyDefinition", static: false }]
}
}
const errors = runRule(rule, "ClassDeclaration", node)
expect(errors).toHaveLength(0)
})
it("should not report for class with non-CallExpression superClass", () => {
const node = {
type: "ClassDeclaration",
superClass: {
type: "Identifier",
name: "BaseClass"
},
body: {
body: [{ type: "PropertyDefinition", static: false }]
}
}
const errors = runRule(rule, "ClassDeclaration", node)
expect(errors).toHaveLength(0)
})
})

View File

@@ -0,0 +1,129 @@
import rule from "@effect/oxc/oxlint/rules/no-unused-internal"
import * as fs from "node:fs"
import * as os from "node:os"
import * as path from "node:path"
import { describe, expect, it } from "vitest"
import { createTestContext } from "./utils.ts"
function writeFixture(files: Record<string, string>): string {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "no-unused-internal-rule-"))
for (const [file, source] of Object.entries(files)) {
const fileName = path.join(cwd, file)
fs.mkdirSync(path.dirname(fileName), { recursive: true })
fs.writeFileSync(fileName, source)
}
return cwd
}
function run(cwd: string, file: string) {
const filename = path.join(cwd, file)
const source = fs.readFileSync(filename, "utf8")
const { context, errors } = createTestContext({ sourceCode: source, cwd, filename })
const visitors = rule.create(context as never)
visitors.Program?.({ type: "Program", range: [0, source.length] } as never)
return errors.map((error) => error.message)
}
describe("no-unused-internal", () => {
it("reports unused top-level @internal exports", () => {
const cwd = writeFixture({
"packages/foo/src/Internal.ts": `/** @internal */
export const unused = 1
`
})
expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([
`@internal export "unused" is not used by production sources`
])
})
it("counts same-file references as usage", () => {
const cwd = writeFixture({
"packages/foo/src/Internal.ts": `/** @internal */
export const used = 1
export const value = used
`
})
expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([])
})
it("counts type-only references as usage", () => {
const cwd = writeFixture({
"packages/foo/src/Internal.ts": `/** @internal */
export interface Internal {
readonly value: string
}
`,
"packages/foo/src/Consumer.ts": `import type { Internal } from "./Internal.ts"
type Local = Internal
`
})
expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([])
})
it("reports @internal exports used in public exported type signatures", () => {
const cwd = writeFixture({
"packages/foo/src/Internal.ts": `/** @internal */
export interface Internal {
readonly value: string
}
`,
"packages/foo/src/Public.ts": `import type { Internal } from "./Internal.ts"
export interface Public {
readonly value: Internal
}
`
})
expect(run(cwd, "packages/foo/src/Public.ts")).toEqual([
`Do not reference @internal export "Internal" in a public exported type signature`
])
expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([])
})
it("ignores @internal class member type signatures", () => {
const cwd = writeFixture({
"packages/foo/src/Internal.ts": `/** @internal */
export interface Internal {
readonly value: string
}
`,
"packages/foo/src/Public.ts": `import type { Internal } from "./Internal.ts"
export class Public {
/** @internal */
getInternal(): Internal {
throw new Error("not implemented")
}
}
`
})
expect(run(cwd, "packages/foo/src/Public.ts")).toEqual([])
expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([])
})
it("ignores public type signatures in internal directories", () => {
const cwd = writeFixture({
"packages/foo/src/Internal.ts": `/** @internal */
export interface Internal {
readonly value: string
}
`,
"packages/foo/src/internal/Consumer.ts": `import type { Internal } from "../Internal.ts"
export interface PublicInsideInternalDirectory {
readonly value: Internal
}
`
})
expect(run(cwd, "packages/foo/src/internal/Consumer.ts")).toEqual([])
expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([])
})
})

View File

@@ -0,0 +1,61 @@
import type { CreateRule, Visitor } from "oxlint"
export interface ReportedError {
node: unknown
message: string
}
export interface TestContextOptions {
sourceCode?: string
filename?: string
cwd?: string
ruleOptions?: Array<unknown>
}
export const createTestContext = (options: TestContextOptions = {}) => {
const {
sourceCode = "",
filename = "/test/file.ts",
cwd = "/test",
ruleOptions = []
} = options
const errors: Array<ReportedError> = []
const context = {
id: "test/rule",
filename,
physicalFilename: filename,
cwd,
options: ruleOptions,
getFilename: () => filename,
getCwd: () => cwd,
report(reportOptions: ReportedError) {
errors.push(reportOptions)
},
sourceCode: {
text: sourceCode,
getText(node?: { range?: [number, number] } | null) {
if (node?.range) {
return sourceCode.slice(node.range[0], node.range[1])
}
return sourceCode
}
}
}
return { errors, context }
}
export const runRule = (
rule: CreateRule,
visitor: keyof Visitor,
node: unknown,
options: TestContextOptions = {}
): Array<ReportedError> => {
const { context, errors } = createTestContext(options)
const visitors = rule.create(context as never)
const handler = visitors[visitor]
if (handler) {
;(handler as (node: unknown) => void)(node)
}
return errors
}