Package Exports
- laint
- laint/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (laint) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
laint
AI Agent Lint Rules SDK - a simple programmatic API for linting JSX/TSX code.
Installation
npm install laintUsage
import { lintJsxCode, getAllRuleNames } from 'laint';
const code = `
<Link href="./profile">Profile</Link>
<Button onPress={() => router.navigate('../settings')} />
`;
// Include mode (default): only run specified rules
const results = lintJsxCode(code, {
rules: ['no-relative-paths', 'no-stylesheet-create', 'expo-image-import']
});
// results:
// [
// { rule: 'no-relative-paths', message: '...', line: 2, column: 14, severity: 'error' },
// { rule: 'no-relative-paths', message: '...', line: 3, column: 41, severity: 'error' }
// ]Exclude Mode
Run all rules except specific ones:
// Exclude mode: run ALL rules except those listed
const results = lintJsxCode(code, {
rules: ['no-stylesheet-create'], // rules to skip
exclude: true
});
// Run all 26 rules
const allResults = lintJsxCode(code, {
rules: [],
exclude: true
});
// Get list of all available rules
const ruleNames = getAllRuleNames(); // ['no-relative-paths', 'expo-image-import', ...]Available Rules (26 total)
Expo Router Rules
| Rule | Severity | Description |
|---|---|---|
no-relative-paths |
error | Use absolute paths in router.navigate/push and Link href |
header-shown-false |
warning | (tabs) Screen in root layout needs headerShown: false |
React Native / Expo Rules
| Rule | Severity | Description |
|---|---|---|
no-stylesheet-create |
warning | Use inline styles instead of StyleSheet.create() |
no-safeareaview |
warning | Use useSafeAreaInsets() hook instead of SafeAreaView |
expo-image-import |
warning | Import Image from expo-image, not react-native |
no-tab-bar-height |
error | Never set explicit height in tabBarStyle |
scrollview-horizontal-flexgrow |
warning | Horizontal ScrollView needs flexGrow: 0 |
expo-font-loaded-check |
error | useFonts() must check loaded before rendering |
tabs-screen-options-header-shown |
warning | Tabs screenOptions should have headerShown: false |
native-tabs-bottom-padding |
warning | NativeTabs screens need 64px bottom padding |
textinput-keyboard-avoiding |
warning | TextInput should be inside KeyboardAvoidingView |
Liquid Glass Rules (expo-glass-effect)
| Rule | Severity | Description |
|---|---|---|
no-border-width-on-glass |
error | No borderWidth on GlassView (breaks borderRadius) |
glass-needs-fallback |
warning | Check isLiquidGlassAvailable() before using GlassView |
glass-interactive-prop |
warning | GlassView in pressables needs isInteractive={true} |
glass-no-opacity-animation |
warning | No opacity animations on GlassView |
React / JSX Rules
| Rule | Severity | Description |
|---|---|---|
no-class-components |
warning | Use function components with hooks |
no-inline-script-code |
error | Script tags should use template literals |
no-react-query-missing |
warning | Use @tanstack/react-query for data fetching |
browser-api-in-useeffect |
warning | window/localStorage only in useEffect for SSR |
fetch-response-ok-check |
warning | Check response.ok when using fetch |
no-complex-jsx-expressions |
warning | Avoid IIFEs and complex expressions in JSX |
Tailwind CSS Rules
| Rule | Severity | Description |
|---|---|---|
no-tailwind-animation-classes |
warning | Avoid animate-* classes, use style jsx global instead |
Backend / SQL Rules
| Rule | Severity | Description |
|---|---|---|
no-require-statements |
error | Use ES imports, not CommonJS require |
no-response-json-lowercase |
warning | Use Response.json() instead of new Response(JSON.stringify()) |
sql-no-nested-calls |
error | Don't nest sql template tags |
General Rules
| Rule | Severity | Description |
|---|---|---|
prefer-lucide-icons |
warning | Prefer lucide-react/lucide-react-native icons |
Rule Details
no-relative-paths
// Bad
router.navigate('./profile');
<Link href="../settings">
// Good
router.navigate('/(tabs)/profile');
<Link href="/settings">browser-api-in-useeffect
// Bad - breaks SSR
function Component() {
const width = window.innerWidth;
return <div>{width}</div>;
}
// Good
function Component() {
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <div>{width}</div>;
}fetch-response-ok-check
// Bad
const response = await fetch('/api/data');
const data = await response.json();
// Good
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();no-response-json-lowercase
// Bad
return new Response(JSON.stringify({ data }));
// Good
return Response.json({ data });tabs-screen-options-header-shown
// Bad
<Tabs screenOptions={{ tabBarStyle: { ... } }}>
// Good
<Tabs screenOptions={{ headerShown: false, tabBarStyle: { ... } }}>native-tabs-bottom-padding
When using NativeTabs from expo-router/unstable-native-tabs, each screen needs 64px bottom padding to prevent content overlap with the tab bar.
textinput-keyboard-avoiding
// Bad - keyboard will cover input
<View>
<TextInput placeholder="Enter text" />
</View>
// Good
<KeyboardAvoidingView>
<TextInput placeholder="Enter text" />
</KeyboardAvoidingView>glass-no-opacity-animation
// Bad - opacity animation causes visual glitches on GlassView
<GlassView style={{ opacity: fadeAnim }} />
// Good - use transform animations instead
<GlassView style={{ transform: [{ scale: scaleAnim }] }} />no-complex-jsx-expressions
// Bad - IIFE in JSX
<div>{(() => { const x = compute(); return x; })()}</div>
// Good - extract to variable
const computedValue = compute();
<div>{computedValue}</div>no-tailwind-animation-classes
// Bad - CSS animation classes have issues
<div className="animate-spin" />
// Good - use style jsx global for animations
<style jsx global>{`
.spinner { animation: spin 1s linear infinite; }
`}</style>
<div className="spinner" />sql-no-nested-calls
// Bad - nested sql causes issues
sql`UPDATE users SET ${sql`name = ${name}`} WHERE id = ${id}`
// Good - build query properly
sql`UPDATE users SET name = ${name} WHERE id = ${id}`Adding a New Rule
- Create a rule file in
src/rules/:
// src/rules/my-rule.ts
import traverse from '@babel/traverse';
import type { File } from '@babel/types';
import type { LintResult } from '../types';
export function myRule(ast: File, code: string): LintResult[] {
const results: LintResult[] = [];
traverse(ast, {
CallExpression(path) {
// Check for violations...
results.push({
rule: 'my-rule',
message: 'Description of the issue',
line: path.node.loc?.start.line ?? 0,
column: path.node.loc?.start.column ?? 0,
severity: 'error', // or 'warning'
});
},
});
return results;
}- Register in
src/rules/index.ts - Add tests in
tests/my-rule.test.ts - Run
npm test
API Reference
lintJsxCode(code: string, config: LintConfig): LintResult[]
Parameters:
code- JSX/TSX source code to lintconfig.rules- Array of rule namesconfig.exclude- (optional) Whentrue, runs all rules except those inrules. Default:false
Returns: Array of LintResult:
interface LintResult {
rule: string;
message: string;
line: number; // 1-indexed
column: number; // 0-indexed
severity: 'error' | 'warning';
}getAllRuleNames(): string[]
Returns an array of all available rule names.
Development
npm install # Install dependencies
npm test # Run tests (169 tests)
npm run build # Build TypeScript