-
Notifications
You must be signed in to change notification settings - Fork 0
TypeScript and JavaScript
lanekeep checks .ts, .mts, .cts, .tsx, .js, .mjs, .cjs and .jsx.
npm install --save-dev lanekeep
npx lanekeep checkOne package per platform plus a launcher that resolves the right one, so you download one binary rather than five. Node is not required to run lanekeep — only to install it this way. The binary has the JavaScript engine compiled in.
pnpm add -D lanekeep, yarn add -D lanekeep and bun add -d lanekeep all work.
lanekeep init in a directory with a package.json or tsconfig.json writes this for you:
{
"$schema": "https://raw.githubusercontent.com/fmsouza/lanekeep/main/schema/lanekeep.schema.json",
"include": ["src/**/*.{ts,tsx}"],
"exclude": ["**/*.{test,spec}.{ts,tsx}"],
"rules": [
"lanekeep/no-default-export",
"./lanekeep/rules/no-debugger.ts"
]
}In a monorepo, include is relative to wherever you run lanekeep:
{ "include": ["packages/*/src/**/*.{ts,tsx}"], "exclude": ["**/dist/**", "**/node_modules/**"] }The grammar is chosen by the file, not by the rule, and typescript and tsx are
different languages here. TypeScript gives up <T>expr casts so the same syntax can open a JSX
element, so one grammar cannot parse both.
A rule declaring language: 'typescript' does not run on .tsx files at all. A rule that
should cover both needs:
language: ['typescript', 'tsx']which is the default when you omit language entirely. Add 'javascript' if the project has
.js too.
This was a real failure: a rule declaring typescript alone, run over a React Native codebase,
matched nothing inside any component and produced 2218 false positives elsewhere — silently, no
error, a tree that "parsed".
A default export has a different name in every importer, so grep stops working and a rename touches nothing. Offers a fix where the export is a named declaration.
{ "rule": "lanekeep/no-restricted-imports", "options": { "restrictions": [
{ "module": "stripe", "from": ["!packages/payments/**"], "reason": "route it through the payments package" },
{ "module": "lodash/*", "reason": "use the standard library" }
] } }from takes globs; a leading ! inverts, so the example means "anywhere except the payments
package". Wildcards work in module.
Cross-file. Finds exported symbols nothing imports — dead surface that keeps being maintained.
Emits facts in check and consumes them in reduce.
Cross-file. Reports import cycles, which make module initialization order load-bearing and undefined-at-import bugs possible.
Both cross-file rules are skipped by --staged and --since, and named on stderr when
they are. A whole-corpus rule over a subset gives a wrong answer, not a smaller one.
import { defineRule } from 'lanekeep'
export default defineRule({
id: 'local/no-numeric-sizes',
// language omitted → ['typescript', 'tsx']
severity: 'error',
card: {
message: 'literal numeric size inside makeStyles',
remediation: 'use theme.spacing.*, theme.borderRadius.* or theme.borders.*',
examples: { bad: 'padding: 12', good: 'padding: theme.spacing.md' },
},
query: `
(pair
key: (property_identifier) @prop
value: [(number) (unary_expression operand: (number))] @value) @match
`,
check(ctx, m) {
if (!/^(padding|margin|gap|borderRadius)/.test(ctx.text(m.prop))) return
if (Number(ctx.text(m.value)) === 0) return
const call = ctx.closestAncestor(m.match, '(call_expression function: (identifier) @f)')
if (!call) return
// The line that makes this a rule rather than a grep: a local `makeStyles` is not
// the one from @rneui/themed.
if (!ctx.resolvesToImport(call.f, { module: '@rneui/themed', name: 'makeStyles' })) return
ctx.report(m.match)
},
})ctx.bindingKind understands import, const, let, var, param, function, class and
catch-param.
ctx.resolvesToImport(node, { module, name }) handles aliasing, so
import { makeStyles as ms } resolves correctly — which is the case a text match gets wrong in
both directions: it misses the alias, and it fires on a local const makeStyles that has
nothing to do with the import.
Languages
In the repository