@inlang/core
Advanced tools
Comparing version 0.4.3 to 0.5.0
@@ -28,3 +28,4 @@ /** | ||
? await (await _fetch(uri)).text() | ||
: (await environment.fs.readFile(`${environment.workingDirectory}/${uri}`, "utf-8")); | ||
: // @ts-ignore - Uses node under the hood which sometimes takes the encoding as a second argument | ||
(await environment.fs.readFile(`${environment.workingDirectory}/${uri}`, "utf-8")); | ||
const moduleWithMimeType = "data:application/javascript;base64," + btoa(moduleAsText); | ||
@@ -31,0 +32,0 @@ return await import(/* @vite-ignore */ moduleWithMimeType); |
@@ -21,2 +21,3 @@ // @vitest-environment jsdom | ||
workingDirectory: "/", | ||
// @ts-ignore | ||
fs: fs.promises, | ||
@@ -42,2 +43,3 @@ fetch, | ||
workingDirectory: "/nested", | ||
// @ts-ignore | ||
fs: fs.promises, | ||
@@ -44,0 +46,0 @@ fetch, |
@@ -1,6 +0,1 @@ | ||
/// <reference types="node" resolution-mode="require"/> | ||
type ReadFileOptions = { | ||
encoding?: BufferEncoding; | ||
flag?: string; | ||
} | BufferEncoding; | ||
/** | ||
@@ -10,6 +5,5 @@ * Minimal filesystem required by inlang to work | ||
export type FS = { | ||
readFile: (id: string, options?: ReadFileOptions) => Promise<string | Buffer>; | ||
readFile: (id: string) => Promise<string>; | ||
writeFile: (file: string, data: string) => Promise<void>; | ||
readdir: (path: string) => Promise<Array<string | object>>; | ||
}; | ||
export {}; |
@@ -0,5 +1,4 @@ | ||
export type { LintRule } from "./rule.js"; | ||
export { createLintRule } from "./createLintRule.js"; | ||
export { getLintReports, hasLintReports } from "./query.js"; | ||
export { lint } from "./linter.js"; | ||
export * from "./query.js"; | ||
export { type Context, parseLintConfigArguments } from "./context.js"; | ||
export { createLintRule, type LintRule, type LintRuleInitializer, type LintRuleId } from "./rule.js"; | ||
export { createLintRuleCollection, type RuleCollectionInitializer } from "./ruleCollection.js"; |
@@ -0,6 +1,4 @@ | ||
export { createLintRule } from "./createLintRule.js"; | ||
export { getLintReports, hasLintReports } from "./query.js"; | ||
export { lint } from "./linter.js"; | ||
export * from "./query.js"; | ||
export { parseLintConfigArguments } from "./context.js"; | ||
export { createLintRule } from "./rule.js"; | ||
export { createLintRuleCollection } from "./ruleCollection.js"; | ||
//# sourceMappingURL=index.js.map |
@@ -1,31 +0,48 @@ | ||
import type { Message } from "../ast/schema.js"; | ||
import type { Config, EnvironmentFunctions } from "../config/schema.js"; | ||
import type { LintRule } from "./rule.js"; | ||
export declare const getLintRulesFromConfig: (config: Config) => LintRule[]; | ||
export declare const lint: (config: Config, env: EnvironmentFunctions) => Promise<{ | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
import type { Config } from "@inlang/core/config"; | ||
import type * as ast from "@inlang/core/ast"; | ||
import type { LintedResource } from "./rule.js"; | ||
/** | ||
* Lints the given resources. | ||
* | ||
* The linted resources will be returned with lint information added to each node. | ||
* The returned errors are unexpected errors that occurred during linting, not | ||
* lint errors themselves. | ||
* | ||
* @example | ||
* const [lintedResources, errors] = await lint({ config, resources }) | ||
* if (errors) { | ||
* // handle unexpected errors during the linting process. | ||
* // this errors are not lint errors themselves! | ||
* } | ||
* const lints = getLintReports(lintedResources, { options }) | ||
*/ | ||
export declare const lint: (args: { | ||
config: Pick<Config, "lint" | "languages" | "referenceLanguage">; | ||
resources: ast.Resource[]; | ||
}) => Promise<[lintedResources: { | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
type: "Resource"; | ||
metadata?: any; | ||
languageTag: import("../ast/schema.js").LanguageTag<{ | ||
languageTag: ast.LanguageTag<{ | ||
Resource: { | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
}; | ||
Message: { | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
}; | ||
Pattern: { | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
}; | ||
}>; | ||
body: Message<{ | ||
body: ast.Message<{ | ||
Resource: { | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
}; | ||
Message: { | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
}; | ||
Pattern: { | ||
lint?: import("./context.js").LintReport[] | undefined; | ||
lint?: import("./rule.js").LintReport[] | undefined; | ||
}; | ||
}>[]; | ||
}[] | undefined>; | ||
}[], errors?: Error[] | undefined]>; |
@@ -1,16 +0,28 @@ | ||
import { createContext } from "./context.js"; | ||
import { createReportFunction } from "./report.js"; | ||
const getResourceForLanguage = (resources, language) => resources.find(({ languageTag }) => languageTag.name === language); | ||
export const getLintRulesFromConfig = (config) => (config.lint?.rules || []).flat(); | ||
export const lint = async (config, env) => { | ||
const { referenceLanguage, languages, readResources } = config; | ||
const lintRules = getLintRulesFromConfig(config); | ||
if (!lintRules.length) { | ||
console.warn("No lint rules specified. Aborting ..."); | ||
return; | ||
/** | ||
* Lints the given resources. | ||
* | ||
* The linted resources will be returned with lint information added to each node. | ||
* The returned errors are unexpected errors that occurred during linting, not | ||
* lint errors themselves. | ||
* | ||
* @example | ||
* const [lintedResources, errors] = await lint({ config, resources }) | ||
* if (errors) { | ||
* // handle unexpected errors during the linting process. | ||
* // this errors are not lint errors themselves! | ||
* } | ||
* const lints = getLintReports(lintedResources, { options }) | ||
*/ | ||
export const lint = async (args) => { | ||
const { referenceLanguage, languages, lint } = args.config; | ||
const resources = structuredClone(args.resources); | ||
if (lint === undefined || lint.rules.length === 0) { | ||
return [args.resources, []]; | ||
} | ||
const resources = await readResources({ config }); | ||
const reference = getResourceForLanguage(resources, referenceLanguage); | ||
await Promise.all(lintRules.map((lintRule) => processLintRule({ | ||
env, | ||
lintRule, | ||
const errors = []; | ||
await Promise.all(lint.rules.flat().map((rule) => processLintRule({ | ||
rule, | ||
referenceLanguage, | ||
@@ -20,57 +32,33 @@ languages, | ||
resources, | ||
}).catch((e) => console.error(`Unexpected error in lint rule '${lintRule.id}':`, e)))); | ||
return resources; | ||
}).catch((e) => errors.push(new Error(`Unexpected error in lint rule '${rule.id}'`, { cause: e }))))); | ||
return [resources, errors.length > 0 ? errors : undefined]; | ||
}; | ||
const processLintRule = async ({ env, lintRule, referenceLanguage, languages, reference, resources, }) => { | ||
const { level, id, setup, visitors, teardown } = lintRule; | ||
if (!level) | ||
return; | ||
const context = createContext(id, level); | ||
const payload = await setup({ env, referenceLanguage, languages, context }); | ||
const processLintRule = async (args) => { | ||
const { referenceLanguage, languages, reference, resources } = args; | ||
const report = createReportFunction(args.rule); | ||
const { visitors } = await args.rule.setup({ config: { referenceLanguage, languages }, report }); | ||
for (const language of languages) { | ||
const target = getResourceForLanguage(resources, language); | ||
await processResource({ | ||
reference, | ||
target: getResourceForLanguage(resources, language), | ||
visitors, | ||
target: target, | ||
reference, | ||
payload, | ||
}); | ||
} | ||
if (teardown) { | ||
await teardown({ payload }); | ||
} | ||
}; | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
const getVisitorFunctions = (visitors, node) => { | ||
const visitor = visitors[node]; | ||
if (!visitor) | ||
return { enter: undefined, leave: undefined }; | ||
if (typeof visitor === "object") | ||
return visitor; | ||
return { enter: visitor, leave: undefined }; | ||
}; | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
const shouldProcessResourceChildren = (visitors) => !!visitors.Message || shouldProcessMessageChildren(visitors); | ||
// TODO: test passing `undefined` for reference | ||
const processResource = async ({ visitors, target, reference, payload: payloadInitial, }) => { | ||
const { enter, leave } = getVisitorFunctions(visitors, "Resource"); | ||
const payloadEnter = (enter && | ||
(await enter({ | ||
target: target, | ||
reference, | ||
payload: payloadInitial, | ||
}))) ?? | ||
payloadInitial; | ||
if (payloadEnter === "skip") | ||
const processResource = async ({ visitors, target, reference, }) => { | ||
const payload = visitors.Resource && (await visitors.Resource({ target: target, reference })); | ||
if (payload === "skip") { | ||
return; | ||
} | ||
// process children | ||
if (shouldProcessResourceChildren(visitors)) { | ||
const processedReferenceMessages = new Set(); | ||
for (const targetMessage of target.body) { | ||
for (const targetMessage of target?.body ?? []) { | ||
const referenceMessage = reference?.body.find(({ id }) => id.name === targetMessage.id.name); | ||
await processMessage({ | ||
visitors, | ||
reference: referenceMessage, | ||
target: targetMessage, | ||
reference: referenceMessage, | ||
payload: payloadEnter, | ||
}); | ||
@@ -81,9 +69,8 @@ if (referenceMessage) { | ||
} | ||
const nonVisitedReferenceMessages = reference.body.filter(({ id }) => !processedReferenceMessages.has(id.name)); | ||
for (const referenceNode of nonVisitedReferenceMessages) { | ||
const nonVisitedReferenceMessages = reference?.body.filter(({ id }) => !processedReferenceMessages.has(id.name)); | ||
for (const referenceNode of nonVisitedReferenceMessages ?? []) { | ||
await processMessage({ | ||
visitors, | ||
reference: referenceNode, | ||
target: undefined, | ||
reference: referenceNode, | ||
payload: payloadEnter, | ||
}); | ||
@@ -93,23 +80,10 @@ processedReferenceMessages.add(referenceNode.id.name); | ||
} | ||
if (leave) { | ||
await leave({ | ||
target: target, | ||
reference, | ||
payload: payloadEnter, | ||
}); | ||
} | ||
}; | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
const shouldProcessMessageChildren = (visitors) => !!visitors.Pattern; | ||
const processMessage = async ({ visitors, target, reference, payload: payloadInitial, }) => { | ||
const { enter, leave } = getVisitorFunctions(visitors, "Message"); | ||
const payloadEnter = (enter && | ||
(await enter({ | ||
target: target, | ||
reference, | ||
payload: payloadInitial, | ||
}))) ?? | ||
payloadInitial; | ||
if (payloadEnter === "skip") | ||
const processMessage = async ({ visitors, target, reference, }) => { | ||
const payload = visitors.Message && (await visitors.Message({ target, reference })); | ||
if (payload === "skip") { | ||
return; | ||
} | ||
// process children | ||
@@ -119,37 +93,14 @@ if (shouldProcessMessageChildren(visitors)) { | ||
visitors, | ||
reference: reference?.pattern, | ||
target: target?.pattern, | ||
reference: reference?.pattern, | ||
payload: payloadEnter, | ||
}); | ||
} | ||
if (leave) { | ||
await leave({ | ||
target: target, | ||
reference, | ||
payload: payloadEnter, | ||
}); | ||
} | ||
}; | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
const processPattern = async ({ visitors, target, reference, payload: payloadInitial, }) => { | ||
const { enter, leave } = getVisitorFunctions(visitors, "Pattern"); | ||
const payloadEnter = (enter && | ||
(await enter({ | ||
target: target, | ||
reference, | ||
payload: payloadInitial, | ||
}))) ?? | ||
payloadInitial; | ||
if (payloadEnter === "skip") | ||
const processPattern = async ({ visitors, target, reference, }) => { | ||
const payload = visitors.Pattern && (await visitors.Pattern({ target, reference })); | ||
if (payload === "skip") { | ||
return; | ||
// we can't really iterate over elements because we can't match them between `target` and `resource` | ||
// to have a consistent API, we allow both `enter` and `leave` even if `leave` does not make sense in this case | ||
if (leave) { | ||
await leave({ | ||
target: target, | ||
reference, | ||
payload: payloadEnter, | ||
}); | ||
} | ||
}; | ||
//# sourceMappingURL=linter.js.map |
import { beforeEach, describe, expect, test, vi } from "vitest"; | ||
import { getLintRulesFromConfig, lint } from "./linter.js"; | ||
import { createLintRule } from "./rule.js"; | ||
describe("getLintRulesFromConfig", async () => { | ||
const rule1 = { id: "rule.1" }; | ||
const rule2 = { id: "rule.2" }; | ||
test("should return an empty `Array` if no lint attribute is present", async () => { | ||
const rules = getLintRulesFromConfig({}); | ||
expect(rules).toHaveLength(0); | ||
}); | ||
test("should return all specified lint rules", async () => { | ||
const rules = getLintRulesFromConfig({ | ||
lint: { rules: [rule1, rule2] }, | ||
}); | ||
expect(rules).toHaveLength(2); | ||
expect(rules[0].id).toBe(rule1.id); | ||
expect(rules[1].id).toBe(rule2.id); | ||
}); | ||
test("should flatten lint rules", async () => { | ||
const rules = getLintRulesFromConfig({ | ||
lint: { rules: [[rule2], rule1] }, | ||
}); | ||
expect(rules).toHaveLength(2); | ||
expect(rules[0].id).toBe(rule2.id); | ||
expect(rules[1].id).toBe(rule1.id); | ||
}); | ||
}); | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
import { lint } from "./linter.js"; | ||
import { createLintRule } from "./createLintRule.js"; | ||
vi.spyOn(console, "info").mockImplementation(vi.fn); | ||
vi.spyOn(console, "warn").mockImplementation(vi.fn); | ||
vi.spyOn(console, "error").mockImplementation(vi.fn); | ||
const dummyEnv = { | ||
$fs: vi.fn(), | ||
$import: vi.fn(), | ||
}; | ||
const doLint = (rules, resources) => { | ||
@@ -40,7 +11,5 @@ const config = { | ||
languages: resources.map((resource) => resource.languageTag.name), | ||
readResources: async () => resources, | ||
writeResources: async () => undefined, | ||
lint: { rules }, | ||
}; | ||
return lint(config, dummyEnv); | ||
return lint({ config, resources }); | ||
}; | ||
@@ -64,26 +33,2 @@ const createResource = (language, ...messages) => ({ | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
const errorRule = { | ||
id: "error.rule", | ||
level: "error", | ||
setup: vi.fn(), | ||
visitors: { | ||
Pattern: vi.fn(), | ||
}, | ||
}; | ||
const warnRule = { | ||
id: "warn.rule", | ||
level: "warn", | ||
setup: vi.fn(), | ||
visitors: { | ||
Pattern: vi.fn(), | ||
}, | ||
}; | ||
const disabledRule = { | ||
id: "disabled.rule", | ||
level: false, | ||
setup: vi.fn(), | ||
visitors: { | ||
Pattern: vi.fn(), | ||
}, | ||
}; | ||
const referenceResource = createResource("en", createMessage("first-message", "Welcome to this app.")); | ||
@@ -96,44 +41,76 @@ const targetResource = createResource("de", createMessage("first-message", "Willkommen zu dieser Applikation.")); | ||
}); | ||
describe("rules", async () => { | ||
test("should be able to disable rule", async () => { | ||
const resources = [referenceResource]; | ||
const result = await doLint([disabledRule], resources); | ||
expect(result).toMatchObject(resources); | ||
expect(disabledRule.visitors.Pattern).toHaveBeenCalledTimes(0); | ||
test("it should be immutable and not modify the resources passed as an argument", async () => { | ||
const cloned = structuredClone(referenceResource); | ||
const rule = createLintRule({ | ||
id: "inlang.someError", | ||
setup: ({ report }) => ({ | ||
visitors: { | ||
Resource: ({ target }) => { | ||
if (target) { | ||
report({ node: target, message: "Error" }); | ||
} | ||
}, | ||
}, | ||
}), | ||
}); | ||
test("should be able to override lint type", async () => { | ||
const rule = createLintRule("error.rule", "error", () => { | ||
let context; | ||
return { | ||
setup: (param) => (context = param.context), | ||
visitors: { | ||
Pattern: ({ target }) => context.report({ node: target, message: "Test" }), | ||
const [result] = await doLint([rule("error")], [cloned]); | ||
expect(cloned).toStrictEqual(referenceResource); | ||
expect(result).not.toStrictEqual(cloned); | ||
}); | ||
test("it should not abort the linting process when errors occur", async () => { | ||
const cloned = structuredClone(referenceResource); | ||
const rule = createLintRule({ | ||
id: "inlang.someError", | ||
setup: () => ({ | ||
visitors: { | ||
Resource: ({ target }) => { | ||
if (target) { | ||
throw new Error("Error"); | ||
} | ||
}, | ||
}; | ||
}); | ||
}, | ||
}), | ||
}); | ||
const [, errors] = await doLint([rule("error")], [cloned]); | ||
expect(errors?.length).toBe(1); | ||
expect(errors[0].message.includes("inlang.someError")); | ||
}); | ||
describe("rules", async () => { | ||
const visitorErrorPatternFn = vi.fn(); | ||
const visitorWarnPatternFn = vi.fn(); | ||
const errorRule = { | ||
id: "error.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Pattern: visitorErrorPatternFn, | ||
}, | ||
}), | ||
}; | ||
const warnRule = { | ||
id: "warn.rule", | ||
level: "warn", | ||
setup: () => ({ | ||
visitors: { | ||
Pattern: visitorWarnPatternFn, | ||
}, | ||
}), | ||
}; | ||
test("should return the original resource if no rules are specified", async () => { | ||
const resources = [referenceResource]; | ||
const result = await doLint([rule("warn")], resources); | ||
expect(result?.[0].body[0].pattern.lint?.[0]).toMatchObject({ | ||
id: "error.rule", | ||
level: "warn", | ||
message: "Test", | ||
}); | ||
const [result] = await doLint([], resources); | ||
expect(result).toEqual(resources); | ||
}); | ||
test("should not start linting if no rules are specified", async () => { | ||
const result = await doLint([], []); | ||
expect(result).toBeUndefined(); | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
}); | ||
test("should process all 'Resources'", async () => { | ||
const resources = [referenceResource, targetResource]; | ||
const result = await doLint([errorRule], resources); | ||
const [result] = await doLint([errorRule], resources); | ||
expect(result).toMatchObject(resources); | ||
expect(errorRule.visitors.Pattern).toHaveBeenCalledTimes(2); | ||
expect(visitorErrorPatternFn).toHaveBeenCalledTimes(2); | ||
}); | ||
test("should process all 'Resources' for all rules", async () => { | ||
const resources = [referenceResource, targetResource]; | ||
const result = await doLint([errorRule, warnRule], resources); | ||
const [result] = await doLint([errorRule, warnRule], resources); | ||
expect(result).toMatchObject(resources); | ||
expect(errorRule.visitors.Pattern).toHaveBeenCalledTimes(2); | ||
expect(warnRule.visitors.Pattern).toHaveBeenCalledTimes(2); | ||
expect(visitorErrorPatternFn).toHaveBeenCalledTimes(2); | ||
expect(visitorWarnPatternFn).toHaveBeenCalledTimes(2); | ||
}); | ||
@@ -144,40 +121,21 @@ }); | ||
const onEnter = vi.fn(); | ||
const onLeave = vi.fn(); | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => Promise.resolve(console.info("setup")), | ||
visitors: { | ||
Resource: { | ||
enter: ({ target }) => { | ||
setup: () => ({ | ||
visitors: { | ||
Resource: ({ target }) => { | ||
onEnter(target); | ||
return Promise.resolve(console.info("Resource enter")); | ||
}, | ||
leave: ({ target }) => { | ||
onLeave(target); | ||
return Promise.resolve(console.info("Resource leave")); | ||
}, | ||
}, | ||
Message: { | ||
enter: ({ target }) => { | ||
Message: ({ target }) => { | ||
onEnter(target); | ||
return Promise.resolve(console.info("Message enter")); | ||
}, | ||
leave: ({ target }) => { | ||
onLeave(target); | ||
return Promise.resolve(console.info("Message leave")); | ||
}, | ||
}, | ||
Pattern: { | ||
enter: ({ target }) => { | ||
Pattern: ({ target }) => { | ||
onEnter(target); | ||
return Promise.resolve(console.info("Pattern enter")); | ||
}, | ||
leave: ({ target }) => { | ||
onLeave(target); | ||
return Promise.resolve(console.info("Pattern leave")); | ||
}, | ||
}, | ||
}, | ||
teardown: () => Promise.resolve(console.info("teardown")), | ||
}), | ||
}; | ||
@@ -191,7 +149,2 @@ test("should visit all nodes exactly once", async () => { | ||
expect(onEnterCalls[2][0]).toMatchObject({ type: "Pattern" }); | ||
expect(onLeave).toHaveBeenCalledTimes(3); | ||
const onLeaveCalls = onLeave.calls; | ||
expect(onLeaveCalls[0][0]).toMatchObject({ type: "Pattern" }); | ||
expect(onLeaveCalls[1][0]).toMatchObject({ type: "Message" }); | ||
expect(onLeaveCalls[2][0]).toMatchObject({ type: "Resource" }); | ||
}); | ||
@@ -205,12 +158,36 @@ test("should visit all Message nodes from reference if not present in target", async () => { | ||
}); | ||
test("should visit all Message nodes from target even if not present in reference", async () => { | ||
const message = createMessage("first-message", "Test"); | ||
const fn = vi.fn(); | ||
await lint({ | ||
config: { | ||
languages: ["en", "de"], | ||
referenceLanguage: "en", | ||
lint: { | ||
rules: [ | ||
{ | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Message: ({ target }) => { | ||
fn(target); | ||
}, | ||
}, | ||
}), | ||
}, | ||
], | ||
}, | ||
}, | ||
resources: [createResource("de", message)], | ||
}); | ||
expect(fn).toHaveBeenCalledTimes(1); | ||
const calls = fn.calls; | ||
expect(calls[0][0]).toStrictEqual(message); | ||
}); | ||
test("should await all functions", async () => { | ||
await doLint([rule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Resource enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "Message enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(4, "Pattern enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(5, "Pattern leave"); | ||
expect(console.info).toHaveBeenNthCalledWith(6, "Message leave"); | ||
expect(console.info).toHaveBeenNthCalledWith(7, "Resource leave"); | ||
expect(console.info).toHaveBeenNthCalledWith(8, "teardown"); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "Resource enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Message enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "Pattern enter"); | ||
}); | ||
@@ -223,65 +200,37 @@ describe("should skip processing children", async () => { | ||
...rule, | ||
visitors: {}, | ||
setup: () => ({ visitors: {} }), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "teardown"); | ||
}); | ||
describe("but not if children has visitor specified", async () => { | ||
test("for Message", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Message: rule.visitors.Message.enter }, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Message enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
}); | ||
test("for Pattern", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Pattern: rule.visitors.Pattern.enter }, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Pattern enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
}); | ||
}); | ||
}); | ||
describe("for Message", async () => { | ||
test("node", async () => { | ||
describe("but not if children has visitor specified", async () => { | ||
test("for Message", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Resource: rule.visitors.Resource.enter }, | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Message: () => { | ||
console.info("Message enter"); | ||
}, | ||
}, | ||
}), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Resource enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "Message enter"); | ||
}); | ||
describe("but not if children has visitor specified", async () => { | ||
test("for Pattern", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Pattern: rule.visitors.Pattern.enter }, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Pattern enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
}); | ||
}); | ||
}); | ||
describe("for Pattern", async () => { | ||
test("node", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Message: rule.visitors.Message.enter }, | ||
test("for Pattern", async () => { | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Pattern: () => { | ||
console.info("Pattern enter"); | ||
}, | ||
}, | ||
}), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Message enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
await doLint([rule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "Pattern enter"); | ||
}); | ||
@@ -292,473 +241,55 @@ }); | ||
test("for 'Resource'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Resource: { | ||
enter: (...param) => { | ||
rule.visitors.Resource.enter(...param); | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Resource: () => { | ||
console.info("Resource enter"); | ||
return "skip"; | ||
}, | ||
leave: rule.visitors.Resource.leave, | ||
Message: () => { | ||
console.info("Message enter"); | ||
}, | ||
}, | ||
Message: rule.visitors.Message.enter, | ||
Pattern: rule.visitors.Pattern.enter, | ||
}, | ||
}), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Resource enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
await doLint([rule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "Resource enter"); | ||
}); | ||
test("for 'Message'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Message: { | ||
enter: (...param) => { | ||
rule.visitors.Message.enter(...param); | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Message: () => { | ||
console.info("Message enter"); | ||
return "skip"; | ||
}, | ||
leave: rule.visitors.Message.leave, | ||
Pattern: () => { | ||
console.info("Pattern enter"); | ||
}, | ||
}, | ||
Pattern: rule.visitors.Pattern.enter, | ||
}, | ||
}), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Message enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
await doLint([rule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "Message enter"); | ||
}); | ||
test("for 'Pattern'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Pattern: { | ||
enter: (...param) => { | ||
rule.visitors.Pattern.enter(...param); | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: () => ({ | ||
visitors: { | ||
Pattern: () => { | ||
console.info("Pattern enter"); | ||
return "skip"; | ||
}, | ||
leave: rule.visitors.Pattern.leave, | ||
}, | ||
}, | ||
}), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "setup"); | ||
expect(console.info).toHaveBeenNthCalledWith(2, "Pattern enter"); | ||
expect(console.info).toHaveBeenNthCalledWith(3, "teardown"); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// ----------------------------------------------------------------------------------------------------------------- | ||
describe("exceptions", async () => { | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: vi.fn(), | ||
visitors: {}, | ||
}; | ||
describe("should not kill process", async () => { | ||
test("if 'teardown' is not present", async () => { | ||
expect(doLint([rule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
describe("for 'Resource'", async () => { | ||
test("if not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Message: vi.fn(), Pattern: vi.fn() }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
test("if 'enter' is not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Resource: { leave: vi.fn() } }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
test("if 'leave' is not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Resource: { enter: vi.fn() } }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
}); | ||
describe("for 'Message'", async () => { | ||
test("if not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Resource: vi.fn(), Pattern: vi.fn() }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
test("if 'enter' is not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Message: { leave: vi.fn() } }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
test("if 'leave' is not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Message: { enter: vi.fn() } }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
}); | ||
describe("for 'Pattern'", async () => { | ||
test("if not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Resource: vi.fn(), Message: vi.fn() }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
test("if 'enter' is not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Pattern: { leave: vi.fn() } }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
test("if 'leave' is not present", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { Pattern: { enter: vi.fn() } }, | ||
}; | ||
expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
}); | ||
}); | ||
describe("if visitor throws", async () => { | ||
describe("in 'Resource'", async () => { | ||
test("'enter'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Resource: { | ||
enter: () => { | ||
throw new Error(); | ||
}, | ||
}, | ||
}, | ||
}; | ||
await expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
}); | ||
test("'leave'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Resource: { | ||
leave: () => { | ||
throw new Error(); | ||
}, | ||
}, | ||
}, | ||
}; | ||
await expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
describe("in 'Message'", async () => { | ||
test("'enter'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Message: { | ||
enter: () => { | ||
throw new Error(); | ||
}, | ||
}, | ||
}, | ||
}; | ||
await expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
}); | ||
test("'leave'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Message: { | ||
leave: () => { | ||
throw new Error(); | ||
}, | ||
}, | ||
}, | ||
}; | ||
await expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
describe("in 'Pattern'", async () => { | ||
test("'enter'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Pattern: { | ||
enter: () => { | ||
throw new Error(); | ||
}, | ||
}, | ||
}, | ||
}; | ||
await expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
}); | ||
test("'leave'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
Pattern: { | ||
leave: () => { | ||
throw new Error(); | ||
}, | ||
}, | ||
}, | ||
}; | ||
await expect(doLint([modifiedRule], [referenceResource])).resolves.not.toThrow(); | ||
expect(console.error).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// ----------------------------------------------------------------------------------------------------------------- | ||
describe("payloads", async () => { | ||
const onEnter = vi.fn(); | ||
const onLeave = vi.fn(); | ||
const rule = { | ||
id: "lint.rule", | ||
level: "error", | ||
setup: (param) => { | ||
onEnter(param); | ||
return { setup: true }; | ||
}, | ||
visitors: { | ||
Resource: { | ||
enter: ({ payload }) => { | ||
onEnter(payload); | ||
return { ...payload, resource: true }; | ||
}, | ||
leave: ({ payload }) => { | ||
onLeave(payload); | ||
}, | ||
}, | ||
Message: { | ||
enter: ({ payload }) => { | ||
onEnter(payload); | ||
return { ...payload, message: true }; | ||
}, | ||
leave: ({ payload }) => { | ||
onLeave(payload); | ||
}, | ||
}, | ||
Pattern: { | ||
enter: ({ payload }) => { | ||
onEnter(payload); | ||
return { ...payload, pattern: true }; | ||
}, | ||
leave: ({ payload }) => { | ||
onLeave(payload); | ||
}, | ||
}, | ||
}, | ||
teardown: ({ payload }) => { | ||
onLeave(payload); | ||
}, | ||
}; | ||
describe("should receive the payload", async () => { | ||
test("in 'setup", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[0][0]; | ||
expect(payload.referenceLanguage).toBe("en"); | ||
expect(payload.languages).toMatchObject(["en"]); | ||
expect(payload.env).toBe(dummyEnv); | ||
expect(payload.context.report).toBeDefined(); | ||
}); | ||
describe("in 'Resource'", async () => { | ||
describe("enter", async () => { | ||
test("from the 'setup' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[1][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
}); | ||
}); | ||
test("'undefined' if no payload returned from 'setup'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
setup: vi.fn(), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[0][0]; | ||
expect(payload).toBeUndefined(); | ||
}); | ||
}); | ||
describe("leave", async () => { | ||
test("from the 'enter' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[2][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
}); | ||
}); | ||
test("from the 'setup' function if no payload returned from 'enter'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
...rule.visitors, | ||
Resource: { ...rule.visitors.Resource, enter: vi.fn() }, | ||
}, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[2][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("in 'Message'", async () => { | ||
describe("enter", async () => { | ||
test("from the 'setup' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[2][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
}); | ||
}); | ||
test("from the 'setup' function if no payload returned from 'Resource'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
...rule.visitors, | ||
Resource: { ...rule.visitors.Resource, enter: vi.fn() }, | ||
}, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[1][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
}); | ||
}); | ||
}); | ||
describe("leave", async () => { | ||
test("from the 'enter' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[1][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
message: true, | ||
}); | ||
}); | ||
test("from the 'setup' function if no payload returned from 'enter'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
...rule.visitors, | ||
Message: { ...rule.visitors.Message, enter: vi.fn() }, | ||
}, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[1][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("in 'Pattern'", async () => { | ||
describe("enter", async () => { | ||
test("from the 'setup' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[3][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
message: true, | ||
}); | ||
}); | ||
test("from the 'Resource' function if no payload returned from 'Message'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
...rule.visitors, | ||
Message: { ...rule.visitors.Message, enter: vi.fn() }, | ||
}, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onEnter | ||
.calls[2][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
}); | ||
}); | ||
}); | ||
describe("leave", async () => { | ||
test("from the 'enter' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[0][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
message: true, | ||
pattern: true, | ||
}); | ||
}); | ||
test("from the 'Message' function if no payload returned from 'enter'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
visitors: { | ||
...rule.visitors, | ||
Pattern: { ...rule.visitors.Pattern, enter: vi.fn() }, | ||
}, | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[0][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
resource: true, | ||
message: true, | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("in 'teardown'", async () => { | ||
test("from the 'setup' function", async () => { | ||
await doLint([rule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[3][0]; | ||
expect(payload).toMatchObject({ | ||
setup: true, | ||
}); | ||
expect(console.info).toHaveBeenNthCalledWith(1, "Pattern enter"); | ||
}); | ||
test("'undefined' if no payload returned from 'setup'", async () => { | ||
const modifiedRule = { | ||
...rule, | ||
setup: vi.fn(), | ||
}; | ||
await doLint([modifiedRule], [referenceResource]); | ||
const payload = onLeave | ||
.calls[3][0]; | ||
expect(payload).toBeUndefined(); | ||
}); | ||
}); | ||
@@ -765,0 +296,0 @@ }); |
@@ -1,3 +0,3 @@ | ||
import type { LintedResource, LintReport } from "./context.js"; | ||
import type { LintedResource, LintReport } from "./rule.js"; | ||
export declare const print: (resource: LintedResource) => void; | ||
export declare const printReport: (report: LintReport, methodOverride?: "info" | "warn" | "error") => void; |
@@ -7,3 +7,3 @@ import { getLintReports, hasLintReports } from "./query.js"; | ||
console.info(separator); | ||
const reports = getLintReports(resource, false); | ||
const reports = getLintReports(resource, { nested: false }); | ||
for (const report of reports) { | ||
@@ -21,3 +21,3 @@ printReport(report, "info"); | ||
console.info(separator); | ||
const reports = getLintReports(message, false); | ||
const reports = getLintReports(message, { nested: false }); | ||
for (const report of reports) { | ||
@@ -31,3 +31,3 @@ printReport(report, "info"); | ||
return; | ||
const reports = getLintReports(pattern, false); | ||
const reports = getLintReports(pattern, { nested: false }); | ||
for (const report of reports) { | ||
@@ -40,6 +40,6 @@ printReport(report, "info"); | ||
return; | ||
const { id, level, message, metadata } = report; | ||
const { id, level, message } = report; | ||
const method = methodOverride ?? (level === "error" ? "error" : "warn"); | ||
console[method](`[${level}] (${id}) ${message}`, metadata ?? ""); | ||
console[method](`[${level}] (${id}) ${message}`); | ||
}; | ||
//# sourceMappingURL=output.js.map |
@@ -12,3 +12,2 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; | ||
message: "Something went wrong again", | ||
metadata: { foo: "bar" }, | ||
}; | ||
@@ -40,10 +39,2 @@ vi.spyOn(console, "warn").mockImplementation(vi.fn); | ||
}); | ||
test("no metadata if not present", async () => { | ||
printReport(report); | ||
expect(console.error.calls[0][0]).not.toContain(`foo`); | ||
}); | ||
test("metadata if present", async () => { | ||
printReport(reportWithMetadata); | ||
expect(console.warn.calls[0][1]).toContain(reportWithMetadata.metadata); | ||
}); | ||
}); | ||
@@ -50,0 +41,0 @@ describe("level", async () => { |
@@ -1,3 +0,2 @@ | ||
import type { LintedNode, LintReport, LintLevel } from "./context.js"; | ||
import type { LintRuleId } from "./rule.js"; | ||
import type { LintedNode, LintReport, LintRule } from "./rule.js"; | ||
/** | ||
@@ -8,65 +7,13 @@ * Extracts all lint reports that are present on the given node. | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.level filter based on the level | ||
* @param options.id filter based on the lint id | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintReports: (node: LintedNode | LintedNode[], nested?: boolean) => LintReport[]; | ||
export declare const getLintReports: (node: LintedNode | LintedNode[], options?: { | ||
level?: LintRule["level"]; | ||
id?: LintRule["id"]; | ||
nested?: boolean; | ||
}) => LintReport[]; | ||
/** | ||
* Extracts all lint reports with a certain lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param level the lint level to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintReportsByLevel: (level: LintLevel, node: LintedNode | LintedNode[], nested?: boolean) => LintReport[]; | ||
/** | ||
* Extracts all lint reports with the 'error' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintErrors: (node: LintedNode | LintedNode[], nested?: boolean | undefined) => LintReport[]; | ||
/** | ||
* Extracts all lint reports with the 'warn' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintWarnings: (node: LintedNode | LintedNode[], nested?: boolean | undefined) => LintReport[]; | ||
/** | ||
* Extracts all lint reports with a certain lint id that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param id the lint id to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintReportsWithId: (id: LintRuleId, node: LintedNode | LintedNode[], nested?: boolean) => LintReport[]; | ||
/** | ||
* Extracts all lint reports with a certain lint id and the 'error' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param id the lint id to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintErrorsWithId: (id: LintRuleId, node: LintedNode | LintedNode[], nested?: boolean) => LintReport[]; | ||
/** | ||
* Extracts all lint reports with a certain lint id and the 'warn' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param id the lint id to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export declare const getLintWarningsWithId: (id: LintRuleId, node: LintedNode | LintedNode[], nested?: boolean) => LintReport[]; | ||
/** | ||
* Checks if a given node has lint reports attached to it. | ||
@@ -76,50 +23,11 @@ * Per default it will also return lint reports attached to child nodes. | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.level filter based on the level | ||
* @param options.id filter based on the lint id | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export declare const hasLintReports: (node: LintedNode | LintedNode[], nested?: boolean) => boolean; | ||
/** | ||
* Checks if a given node has lint reports with the 'error' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export declare const hasLintErrors: (node: LintedNode | LintedNode[], nested?: boolean) => boolean; | ||
/** | ||
* Checks if a given node has lint reports with the 'warn' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export declare const hasLintWarnings: (node: LintedNode | LintedNode[], nested?: boolean) => boolean; | ||
/** | ||
* Checks if a given node has lint reports with a certain lint id attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export declare const hasLintReportsWithId: (id: LintRuleId, node: LintedNode | LintedNode[], nested?: boolean) => boolean; | ||
/** | ||
* Checks if a given node has lint reports with a certain lint id and the 'error' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export declare const hasLintErrorsWithId: (id: LintRuleId, node: LintedNode | LintedNode[], nested?: boolean) => boolean; | ||
/** | ||
* Checks if a given node has lint reports with a certain lint id and the 'warn' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export declare const hasLintWarningsWithId: (id: LintRuleId, node: LintedNode | LintedNode[], nested?: boolean) => boolean; | ||
export declare const hasLintReports: (node: LintedNode | LintedNode[], options?: { | ||
level?: LintRule["level"]; | ||
id?: LintRule["id"]; | ||
nested?: boolean; | ||
}) => boolean; |
@@ -1,2 +0,1 @@ | ||
import { unhandled } from "../utilities/error-handling.js"; | ||
/** | ||
@@ -7,8 +6,14 @@ * Extracts all lint reports that are present on the given node. | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.level filter based on the level | ||
* @param options.id filter based on the lint id | ||
* @returns a list of lint reports | ||
*/ | ||
export const getLintReports = (node, nested = true) => { | ||
export const getLintReports = (node, | ||
// Writing out QueryOptions for a better DX. | ||
// Developers can see the options instead of the type. | ||
options) => { | ||
const withDefaults = { nested: true, ...options }; | ||
if (Array.isArray(node)) { | ||
return node.flatMap((n) => getLintReports(n, nested)); | ||
return node.flatMap((n) => getLintReports(n, withDefaults)); | ||
} | ||
@@ -18,79 +23,35 @@ const { type } = node; | ||
case "Resource": | ||
return getLintReportsFromResource(node, nested); | ||
return withFilters(getLintReportsFromResource(node, withDefaults), withDefaults); | ||
case "Message": | ||
return getLintReportsFromMessage(node, nested); | ||
return withFilters(getLintReportsFromMessage(node, withDefaults), withDefaults); | ||
case "Pattern": | ||
return getLintReportsFromPattern(node); | ||
return withFilters(getLintReportsFromPattern(node), withDefaults); | ||
default: | ||
return unhandled(type, node); | ||
// If an unhandled node type is encountered, it is a bug in inlang's source code. | ||
// The lint implementation is responsible for handling all node types, not user land. | ||
throw new Error(`Unhandled linted node type: ${type}. You stumbled upon a bug in inlang's source code. Please open an issue on GitHub`); | ||
} | ||
}; | ||
const getLintReportsFromResource = ({ lint, body }, nested) => [ | ||
const getLintReportsFromResource = ({ lint, body }, options) => [ | ||
...(lint || []), | ||
...(nested ? body.flatMap((message) => getLintReportsFromMessage(message, nested)) : []), | ||
...(options.nested ? body.flatMap((message) => getLintReportsFromMessage(message, options)) : []), | ||
]; | ||
const getLintReportsFromMessage = ({ lint, pattern }, nested) => [...(lint || []), ...(nested ? getLintReportsFromPattern(pattern) : [])]; | ||
const getLintReportsFromMessage = ({ lint, pattern }, options) => [...(lint || []), ...(options.nested ? getLintReportsFromPattern(pattern) : [])]; | ||
const getLintReportsFromPattern = ({ lint }) => lint || []; | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
/** | ||
* Extracts all lint reports with a certain lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param level the lint level to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
* Applies filters based on the QueryOptions to a lint report. | ||
*/ | ||
export const getLintReportsByLevel = (level, node, nested = true) => getLintReports(node, nested).filter((report) => report.level === level); | ||
/** | ||
* Extracts all lint reports with the 'error' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export const getLintErrors = getLintReportsByLevel.bind(undefined, "error"); | ||
/** | ||
* Extracts all lint reports with the 'warn' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export const getLintWarnings = getLintReportsByLevel.bind(undefined, "warn"); | ||
const withFilters = (report, options) => { | ||
return report.filter((report) => { | ||
if (options.id && report.id !== options.id) { | ||
return false; | ||
} | ||
else if (options.level && report.level !== options.level) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
}; | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
/** | ||
* Extracts all lint reports with a certain lint id that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param id the lint id to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export const getLintReportsWithId = (id, node, nested = true) => getLintReports(node, nested).filter((report) => report.id === id); | ||
/** | ||
* Extracts all lint reports with a certain lint id and the 'error' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param id the lint id to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export const getLintErrorsWithId = (id, node, nested = true) => getLintErrors(node, nested).filter((report) => report.id === id); | ||
/** | ||
* Extracts all lint reports with a certain lint id and the 'warn' lint level that are present on the given node. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param id the lint id to filter | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns a list of lint reports | ||
*/ | ||
export const getLintWarningsWithId = (id, node, nested = true) => getLintWarnings(node, nested).filter((report) => report.id === id); | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
/** | ||
* Checks if a given node has lint reports attached to it. | ||
@@ -100,51 +61,10 @@ * Per default it will also return lint reports attached to child nodes. | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.nested if set to `false` will just return the lint reports directly attached on this node | ||
* @param options.level filter based on the level | ||
* @param options.id filter based on the lint id | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export const hasLintReports = (node, nested = true) => getLintReports(node, nested).length > 0; | ||
/** | ||
* Checks if a given node has lint reports with the 'error' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export const hasLintErrors = (node, nested = true) => getLintErrors(node, nested).length > 0; | ||
/** | ||
* Checks if a given node has lint reports with the 'warn' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export const hasLintWarnings = (node, nested = true) => getLintErrors(node, nested).length > 0; | ||
/** | ||
* Checks if a given node has lint reports with a certain lint id attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export const hasLintReportsWithId = (id, node, nested = true) => getLintReportsWithId(id, node, nested).length > 0; | ||
/** | ||
* Checks if a given node has lint reports with a certain lint id and the 'error' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export const hasLintErrorsWithId = (id, node, nested = true) => getLintErrorsWithId(id, node, nested).length > 0; | ||
/** | ||
* Checks if a given node has lint reports with a certain lint id and the 'warn' lint level attached to it. | ||
* Per default it will also return lint reports attached to child nodes. | ||
* | ||
* @param node the node to extract lint reports from | ||
* @param nested if set to `false` will just return the lint reports directly attached on this node | ||
* @returns `true` iff the given node has lint reports | ||
*/ | ||
export const hasLintWarningsWithId = (id, node, nested = true) => getLintWarningsWithId(id, node, nested).length > 0; | ||
export const hasLintReports = (node, // Writing out QueryOptions for a better DX. | ||
// Developers can see the options instead of the type. | ||
options) => getLintReports(node, options).length > 0; | ||
//# sourceMappingURL=query.js.map |
import { describe, expect, test } from "vitest"; | ||
import { getLintErrors, getLintErrorsWithId, getLintReports, getLintReportsByLevel, getLintReportsWithId, getLintWarnings, getLintWarningsWithId, hasLintErrors, hasLintErrorsWithId, hasLintReports, hasLintReportsWithId, hasLintWarnings, hasLintWarningsWithId, } from "./query.js"; | ||
import { getLintReports, hasLintReports } from "./query.js"; | ||
const createReport = (id, level) => ({ | ||
@@ -39,444 +39,48 @@ id, | ||
const resource = createLintedResource("de", [createReport("id.3", "error"), createReport("id.1", "warn"), createReport("id.4", "error")], message, message2, message3); | ||
const resource2 = createLintedResource("de", undefined, message3); | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
describe("getLintReports", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReports(resource, true); | ||
const reports = getLintReports(resource, { nested: true }); | ||
expect(reports).toHaveLength(12); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReports(resource, false); | ||
const reports = getLintReports(resource, { nested: false }); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReports(message, true); | ||
test("by level nested", async () => { | ||
const reports = getLintReports(resource, { nested: true, level: "error" }); | ||
expect(reports).toHaveLength(6); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReports(message, false); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReports(pattern, true); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReports(pattern, false); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
}); | ||
test("should support an array of nodes", async () => { | ||
const reports = getLintReports([resource, message, pattern]); | ||
expect(reports).toHaveLength(21); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintReports({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
describe("getLintReportsByLevel", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReportsByLevel("error", resource, true); | ||
expect(reports).toHaveLength(6); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReportsByLevel("error", resource, false); | ||
test("by level not nested", async () => { | ||
const reports = getLintReports(resource, { nested: false, level: "error" }); | ||
expect(reports).toHaveLength(2); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReportsByLevel("error", message, true); | ||
expect(reports).toHaveLength(2); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReportsByLevel("error", message, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReportsByLevel("error", pattern, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReportsByLevel("error", pattern, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintReportsByLevel("warn", { | ||
type: "unknown", | ||
})).toThrow(); | ||
}); | ||
}); | ||
describe("getLintErrors", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintErrors(resource, true); | ||
expect(reports).toHaveLength(6); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintErrors(resource, false); | ||
expect(reports).toHaveLength(2); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintErrors(message, true); | ||
expect(reports).toHaveLength(2); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintErrors(message, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintErrors(pattern, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintErrors(pattern, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintErrors({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
describe("getLintWarnings", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintWarnings(resource, true); | ||
expect(reports).toHaveLength(6); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintWarnings(resource, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintWarnings(message2, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintWarnings(message2, false); | ||
expect(reports).toHaveLength(0); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintWarnings(pattern, true); | ||
expect(reports).toHaveLength(2); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintWarnings(pattern, false); | ||
expect(reports).toHaveLength(2); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintWarnings({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
describe("getLintReportsWithId", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReportsWithId("id.3", resource, true); | ||
test("with id nested", async () => { | ||
const reports = getLintReports(resource, { nested: true, id: "id.3" }); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReportsWithId("id.3", resource, false); | ||
test("with id not nested", async () => { | ||
const reports = getLintReports(resource, { nested: false, id: "id.3" }); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReportsWithId("id.2", message, true); | ||
expect(reports).toHaveLength(1); | ||
test("should support an array of nodes", async () => { | ||
const reports = getLintReports([resource, message, pattern]); | ||
expect(reports).toHaveLength(21); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReportsWithId("id.2", message, false); | ||
expect(reports).toHaveLength(0); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintReports({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintReportsWithId("id.1", pattern, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintReportsWithId("id.1", pattern, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("Message", async () => { | ||
const reports = getLintReports(message); | ||
expect(reports).toHaveLength(6); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintReportsWithId("some.id", { | ||
type: "unknown", | ||
})).toThrow(); | ||
test("'Pattern'", async () => { | ||
const reports = getLintReports(pattern); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
}); | ||
describe("getLintErrorsWithId", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintErrorsWithId("id.3", resource, true); | ||
expect(reports).toHaveLength(3); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintErrorsWithId("id.3", resource, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintErrorsWithId("id.2", message, true); | ||
expect(reports).toHaveLength(0); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintErrorsWithId("id.2", message, false); | ||
expect(reports).toHaveLength(0); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintErrorsWithId("id.1", pattern, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintErrorsWithId("id.1", pattern, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintErrorsWithId("some.id", { | ||
type: "unknown", | ||
})).toThrow(); | ||
}); | ||
test("hasLintReports()", async () => { | ||
expect(hasLintReports(resource)).toBe(true); | ||
}); | ||
describe("getLintWarningsWithId", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintWarningsWithId("id.5", resource, true); | ||
expect(reports).toHaveLength(0); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintWarningsWithId("id.5", resource, false); | ||
expect(reports).toHaveLength(0); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintWarningsWithId("id.2", message, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintWarningsWithId("id.2", message, false); | ||
expect(reports).toHaveLength(0); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
const reports = getLintWarningsWithId("id.2", pattern, true); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
test("not nested", async () => { | ||
const reports = getLintWarningsWithId("id.2", pattern, false); | ||
expect(reports).toHaveLength(1); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => getLintWarningsWithId("some.id", { | ||
type: "unknown", | ||
})).toThrow(); | ||
}); | ||
}); | ||
// -------------------------------------------------------------------------------------------------------------------- | ||
describe("hasLintReports", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintReports(resource, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintReports(resource, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintReports(message, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintReports(message, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintReports(pattern, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintReports(pattern, false)).toBe(true); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintReports({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
describe("hasLintErrors", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintErrors(resource, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintErrors(resource, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintErrors(message, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintErrors(message, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintErrors(pattern, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintErrors(pattern, false)).toBe(true); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintErrors({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
describe("hasLintWarnings", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintWarnings(resource, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintWarnings(resource, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintWarnings(message, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintWarnings(message, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintWarnings(pattern, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintWarnings(pattern, false)).toBe(true); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintWarnings({ type: "unknown" })).toThrow(); | ||
}); | ||
}); | ||
describe("hasLintReportsWithId", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintReportsWithId("id.4", resource, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintReportsWithId("id.4", resource, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintReportsWithId("id.4", message, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintReportsWithId("id.4", message, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintReportsWithId("id.4", pattern, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintReportsWithId("id.4", pattern, false)).toBe(true); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintReportsWithId("id.1", { | ||
type: "unknown", | ||
})).toThrow(); | ||
}); | ||
}); | ||
describe("hasLintErrorsWithId", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintErrorsWithId("id.1", resource2, true)).toBe(false); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintErrorsWithId("id.1", resource2, false)).toBe(false); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintErrorsWithId("id.1", message, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintErrorsWithId("id.1", message, false)).toBe(false); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintErrorsWithId("id.1", pattern, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintErrorsWithId("id.1", pattern, false)).toBe(true); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintErrorsWithId("id.1", { | ||
type: "unknown", | ||
})).toThrow(); | ||
}); | ||
}); | ||
describe("hasLintWarningsWithId", async () => { | ||
describe("'Resource'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintWarningsWithId("id.1", resource, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintWarningsWithId("id.1", resource, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Message'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintWarningsWithId("id.1", message, true)).toBe(true); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintWarningsWithId("id.1", message, false)).toBe(true); | ||
}); | ||
}); | ||
describe("'Pattern'", async () => { | ||
test("nested", async () => { | ||
expect(hasLintWarningsWithId("id.1", pattern, true)).toBe(false); | ||
}); | ||
test("not nested", async () => { | ||
expect(hasLintWarningsWithId("id.1", pattern, false)).toBe(false); | ||
}); | ||
}); | ||
test("should throw an error if node type does not get handled", async () => { | ||
expect(() => hasLintWarningsWithId("id.1", { | ||
type: "unknown", | ||
})).toThrow(); | ||
}); | ||
}); | ||
//# sourceMappingURL=query.test.js.map |
@@ -1,109 +0,56 @@ | ||
import type { Message, Pattern, Resource } from "../ast/index.js"; | ||
import type { Config, EnvironmentFunctions } from "../config/schema.js"; | ||
import type { MaybePromise } from "../utilities/types.js"; | ||
import { LintLevel, Context } from "./context.js"; | ||
export type LintableNode = Resource | Message | Pattern; | ||
export type LintableNodeByType<Node extends { | ||
type: string; | ||
}, Key> = Node extends { | ||
type: Key; | ||
} ? Node : never; | ||
export type TargetReferenceParameterTuple<Node extends LintableNode> = { | ||
target: Node; | ||
reference: Node; | ||
} | { | ||
target: Node; | ||
reference: Node | undefined; | ||
} | { | ||
target: Node | undefined; | ||
reference: Node; | ||
}; | ||
type VisitorParam<Node extends LintableNode, Input> = TargetReferenceParameterTuple<Node> & { | ||
payload?: Input; | ||
}; | ||
export type EnterNodeFunction<Node extends LintableNode, Input, Output> = (param: VisitorParam<Node, Input>) => MaybePromise<"skip" | void | Output>; | ||
export type LeaveNodeFunction<Node extends LintableNode, Input> = (param: VisitorParam<Node, Input>) => MaybePromise<"skip" | void>; | ||
export type NodeVisitor<Node extends LintableNode> = EnterNodeFunction<Node, any, any> | { | ||
enter?: EnterNodeFunction<Node, any, any>; | ||
leave?: LeaveNodeFunction<Node, any>; | ||
}; | ||
export type NodeVisitors = { | ||
[Key in LintableNode["type"]]?: NodeVisitor<LintableNodeByType<LintableNode, Key>>; | ||
}; | ||
export type LintConfigArguments<Settings = never, RequireSettings extends boolean = false> = RequireSettings extends true ? [boolean | LintLevel, Settings] : [] | [boolean | LintLevel] | [boolean | LintLevel, Settings?]; | ||
import type { Config } from "../config/index.js"; | ||
import type * as ast from "../ast/index.js"; | ||
import type { createReportFunction } from "./report.js"; | ||
/** | ||
* The unique id of a lint rule. | ||
* | ||
* @example | ||
* ``` | ||
* 'inlangStandardRules.missingKey' | ||
* ``` | ||
*/ | ||
export type LintRuleId = `${string}.${string}`; | ||
/** | ||
* An utility type to add strong type definitions for a lint rule. | ||
* | ||
* @example a rule that does not expects any settings | ||
* ``` | ||
* const myRule: LintRuleInitializer = // implementation | ||
* ``` | ||
* @example a rule that accepts settings | ||
* ``` | ||
* const myRule: LintRuleInitializer<{ strict: boolean }> = // implementation | ||
* ``` | ||
* @example a rule that requires settings | ||
* ``` | ||
* const myRule: LintRuleInitializer<{ strict: boolean }, true> = // implementation | ||
* ``` | ||
*/ | ||
export type LintRuleInitializer<Settings = never, RequireSettings extends boolean = false> = (...args: LintConfigArguments<Settings, RequireSettings>) => LintRule; | ||
/** | ||
* A lint rule that was configured with the lint level and lint specific settings. | ||
*/ | ||
export type LintRule = { | ||
id: LintRuleId; | ||
level: false | LintLevel; | ||
setup: (param: Pick<Config, "referenceLanguage" | "languages"> & { | ||
env: EnvironmentFunctions; | ||
context: Context; | ||
}) => MaybePromise<unknown>; | ||
visitors: NodeVisitors; | ||
teardown?: (param: { | ||
payload: unknown; | ||
}) => MaybePromise<void>; | ||
id: `${string}.${string}`; | ||
level: "error" | "warn"; | ||
setup: (args: { | ||
config: Pick<Config, "referenceLanguage" | "languages">; | ||
report: ReturnType<typeof createReportFunction>; | ||
}) => MaybePromise<{ | ||
visitors: Visitors; | ||
}>; | ||
}; | ||
export type Visitors = { | ||
Resource?: VisitorFunction<ast.Resource>; | ||
Message?: VisitorFunction<ast.Message>; | ||
Pattern?: VisitorFunction<ast.Pattern>; | ||
}; | ||
/** | ||
* An utility function that encapsulates the parsing of the arguments passed to the lint rule. | ||
* A report of a given lint rule. | ||
*/ | ||
export type LintReport = { | ||
id: LintRule["id"]; | ||
level: LintRule["level"]; | ||
message: string; | ||
}; | ||
/** | ||
* Nodes that can be linted. | ||
* | ||
* @param id the unique lint id of this lint rule | ||
* @param defaultLevel the default lint level this rule should have | ||
* @param configureLintRule a callback function that get passed the arguments and need to return rule specific implementation details | ||
* @returns a lint rule | ||
* | ||
* @example | ||
* ``` | ||
* const myRule = createLintRule<{ strict: boolean }>('my.rule', 'error', (settings) => { | ||
* return { | ||
* setup: () => { | ||
* if (settings?.strict) return { token: '123' } | ||
* | ||
* return { token: '456' } | ||
* }, | ||
* visitors: {} | ||
* } | ||
* }) | ||
* ``` | ||
* The linter will only lint nodes that are of this type. | ||
*/ | ||
export declare const createLintRule: <Settings = never, RequireSettings extends boolean = false>(id: LintRuleId, defaultLevel: LintLevel, configureLintRule: (settings?: Settings | undefined) => Omit<LintRule, "id" | "level">) => (...args: LintConfigArguments<Settings, RequireSettings>) => { | ||
id: `${string}.${string}`; | ||
level: false | LintLevel; | ||
setup: (param: Pick<Config, "referenceLanguage" | "languages"> & { | ||
env: EnvironmentFunctions; | ||
context: Context; | ||
}) => MaybePromise<unknown>; | ||
visitors: NodeVisitors; | ||
teardown?: ((param: { | ||
payload: unknown; | ||
}) => MaybePromise<void>) | undefined; | ||
export type LintableNode = ast.Resource | ast.Message | ast.Pattern; | ||
type VisitorFunction<Node extends LintableNode> = (args: { | ||
reference?: Node; | ||
target?: Node; | ||
}) => MaybePromise<void | "skip">; | ||
type LintInformation = { | ||
lint?: LintReport[]; | ||
}; | ||
type LintExtension = { | ||
Resource: LintInformation; | ||
Message: LintInformation; | ||
Pattern: LintInformation; | ||
}; | ||
export type LintedResource = Pretty<ast.Resource<LintExtension>>; | ||
export type LintedMessage = Pretty<ast.Message<LintExtension>>; | ||
export type LintedPattern = Pretty<ast.Pattern<LintExtension>>; | ||
export type LintedNode = LintedResource | LintedMessage | LintedPattern; | ||
type MaybePromise<T> = T | Promise<T>; | ||
type Pretty<T> = T extends (...args: any[]) => any ? T : T extends abstract new (...args: any[]) => any ? T : { | ||
[K in keyof T]: T[K]; | ||
}; | ||
export {}; |
@@ -1,32 +0,2 @@ | ||
import { parseLintConfigArguments } from "./context.js"; | ||
/** | ||
* An utility function that encapsulates the parsing of the arguments passed to the lint rule. | ||
* | ||
* @param id the unique lint id of this lint rule | ||
* @param defaultLevel the default lint level this rule should have | ||
* @param configureLintRule a callback function that get passed the arguments and need to return rule specific implementation details | ||
* @returns a lint rule | ||
* | ||
* @example | ||
* ``` | ||
* const myRule = createLintRule<{ strict: boolean }>('my.rule', 'error', (settings) => { | ||
* return { | ||
* setup: () => { | ||
* if (settings?.strict) return { token: '123' } | ||
* | ||
* return { token: '456' } | ||
* }, | ||
* visitors: {} | ||
* } | ||
* }) | ||
* ``` | ||
*/ | ||
export const createLintRule = (id, defaultLevel, configureLintRule) => ((...args) => { | ||
const { level, settings } = parseLintConfigArguments(args, defaultLevel); | ||
return { | ||
...configureLintRule(settings), | ||
id, | ||
level, | ||
}; | ||
}); | ||
export {}; | ||
//# sourceMappingURL=rule.js.map |
@@ -1,1 +0,1 @@ | ||
export * from "./result.js"; | ||
export { Result } from "./result.js"; |
@@ -1,2 +0,2 @@ | ||
export * from "./result.js"; | ||
export { Result } from "./result.js"; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@inlang/core", | ||
"type": "module", | ||
"version": "0.4.3", | ||
"version": "0.5.0", | ||
"publishConfig": { | ||
@@ -25,6 +25,2 @@ "access": "public" | ||
}, | ||
"./lint/test-utilities": { | ||
"types": "./dist/lint/test-utilities.d.ts", | ||
"import": "./dist/lint/test-utilities.js" | ||
}, | ||
"./query": { | ||
@@ -31,0 +27,0 @@ "types": "./dist/query/index.d.ts", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
137994
80
2239
3