Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:

- run: pnpm install --frozen-lockfile

- name: Install Playwright Chromium (website e2e)
run: pnpm --filter website exec playwright install chromium --with-deps

- run: pnpm test

- run: pnpm lint
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"scripts": {
"dev": "pnpm --filter react-doctor run dev",
"build": "pnpm --filter react-doctor run build",
"test": "pnpm --filter react-doctor run test",
"test": "pnpm --filter react-doctor-core run test && pnpm --filter react-doctor-web run typecheck && pnpm --filter react-doctor run test && pnpm --filter website run test:e2e",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"format": "oxfmt",
"format:check": "oxfmt --check",
"typecheck": "pnpm --filter react-doctor run typecheck && pnpm --filter react-doctor-core run typecheck && pnpm --filter react-doctor-web run typecheck && pnpm --filter website exec tsc --noEmit",
"changeset": "changeset",
"version": "changeset version",
"release": "pnpm build && changeset publish"
Expand Down
30 changes: 30 additions & 0 deletions packages/react-doctor-core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "react-doctor-core",
"version": "0.0.38",
"description": "Core package for react doctor",
"license": "MIT",
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./browser": {
"types": "./src/browser.ts",
"default": "./src/browser.ts"
},
"./plugin/constants": {
"types": "./src/plugin/constants.ts",
"default": "./src/plugin/constants.ts"
}
},
"scripts": {
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^6.0.2",
"vitest": "^4.1.4"
}
}
30 changes: 30 additions & 0 deletions packages/react-doctor-core/src/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export {
type CleanedDiagnostic,
type Diagnostic,
type OxlintDiagnostic,
type OxlintLabel,
type OxlintOutput,
type OxlintSpan,
type ScoreResult,
} from "./types";
export {
ERROR_PREVIEW_LENGTH_CHARS,
ERROR_RULE_PENALTY,
JSX_FILE_PATTERN,
PERFECT_SCORE,
SCORE_GOOD_THRESHOLD,
SCORE_OK_THRESHOLD,
WARNING_RULE_PENALTY,
} from "./constants";
export {
calculateScoreLocally,
countUniqueRules,
getScoreLabel,
scoreFromRuleCounts,
} from "./score";
export {
cleanDiagnosticMessage,
parseOxlintOutput,
parseRuleCode,
resolveDiagnosticCategory,
} from "./oxlint-output";
13 changes: 13 additions & 0 deletions packages/react-doctor-core/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;

export const ERROR_PREVIEW_LENGTH_CHARS = 200;

export const PERFECT_SCORE = 100;

export const SCORE_GOOD_THRESHOLD = 75;

export const SCORE_OK_THRESHOLD = 50;

export const ERROR_RULE_PENALTY = 1.5;

export const WARNING_RULE_PENALTY = 0.75;
31 changes: 31 additions & 0 deletions packages/react-doctor-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export {
type CleanedDiagnostic,
type Diagnostic,
type OxlintDiagnostic,
type OxlintLabel,
type OxlintOutput,
type OxlintSpan,
type ScoreResult,
} from "./types";
export {
ERROR_PREVIEW_LENGTH_CHARS,
ERROR_RULE_PENALTY,
JSX_FILE_PATTERN,
PERFECT_SCORE,
SCORE_GOOD_THRESHOLD,
SCORE_OK_THRESHOLD,
WARNING_RULE_PENALTY,
} from "./constants";
export {
calculateScoreLocally,
countUniqueRules,
getScoreLabel,
scoreFromRuleCounts,
} from "./score";
export {
cleanDiagnosticMessage,
parseOxlintOutput,
parseRuleCode,
resolveDiagnosticCategory,
} from "./oxlint-output";
export { default as reactDoctorOxlintPlugin } from "./plugin/index";
405 changes: 405 additions & 0 deletions packages/react-doctor-core/src/oxlint-output.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
MUTATION_METHOD_NAMES,
SETTER_PATTERN,
UPPERCASE_PATTERN,
} from "./constants.js";
import type { EsTreeNode, RuleVisitors } from "./types.js";
} from "./constants";
import type { EsTreeNode, RuleVisitors } from "./types";

export const walkAst = (node: EsTreeNode, visitor: (child: EsTreeNode) => void): void => {
if (!node || typeof node !== "object") return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import {
noGiantComponent,
noNestedComponentDefinition,
noRenderInRender,
} from "./rules/architecture.js";
} from "./rules/architecture";
import {
noBarrelImport,
noFullLodashImport,
noMoment,
noUndeferredThirdParty,
preferDynamicImport,
useLazyMotion,
} from "./rules/bundle-size.js";
import { clientPassiveEventListeners } from "./rules/client.js";
} from "./rules/bundle-size";
import { clientPassiveEventListeners } from "./rules/client";
import {
noDarkModeGlow,
noDisabledZoom,
Expand All @@ -28,12 +28,12 @@ import {
noTinyText,
noWideLetterSpacing,
noZIndex9999,
} from "./rules/design.js";
} from "./rules/design";
import {
noArrayIndexAsKey,
noPreventDefault,
renderingConditionalRender,
} from "./rules/correctness.js";
} from "./rules/correctness";
import {
asyncParallel,
jsBatchDomCss,
Expand All @@ -46,7 +46,7 @@ import {
jsSetMapLookups,
jsTosortedImmutable,
jsFlatmapFilter,
} from "./rules/js-performance.js";
} from "./rules/js-performance";
import {
nextjsAsyncClientComponent,
nextjsImageMissingSizes,
Expand All @@ -64,7 +64,7 @@ import {
nextjsNoRedirectInTryCatch,
nextjsNoSideEffectInGetHandler,
nextjsNoUseSearchParamsWithoutSuspense,
} from "./rules/nextjs.js";
} from "./rules/nextjs";
import {
noGlobalCssVariableAnimation,
noLargeAnimatedBlur,
Expand All @@ -78,7 +78,7 @@ import {
renderingHydrationNoFlicker,
renderingScriptDeferAsync,
rerenderMemoWithDefaultValue,
} from "./rules/performance.js";
} from "./rules/performance";
import {
rnNoRawText,
rnNoDeprecatedModules,
Expand All @@ -88,17 +88,17 @@ import {
rnNoLegacyShadowStyles,
rnPreferReanimated,
rnNoSingleElementStyleArray,
} from "./rules/react-native.js";
} from "./rules/react-native";
import {
queryMutationMissingInvalidation,
queryNoQueryInEffect,
queryNoRestDestructuring,
queryNoUseQueryForMutation,
queryNoVoidQueryFn,
queryStableQueryClient,
} from "./rules/tanstack-query.js";
import { noEval, noSecretsInClientCode } from "./rules/security.js";
import { serverAfterNonblocking, serverAuthActions } from "./rules/server.js";
} from "./rules/tanstack-query";
import { noEval, noSecretsInClientCode } from "./rules/security";
import { serverAfterNonblocking, serverAuthActions } from "./rules/server";
import {
tanstackStartGetMutation,
tanstackStartLoaderParallelFetch,
Expand All @@ -114,7 +114,7 @@ import {
tanstackStartRoutePropertyOrder,
tanstackStartServerFnMethodOrder,
tanstackStartServerFnValidateInput,
} from "./rules/tanstack-start.js";
} from "./rules/tanstack-start";
import {
noCascadingSetState,
noDerivedStateEffect,
Expand All @@ -125,8 +125,8 @@ import {
rerenderDependencies,
rerenderFunctionalSetstate,
rerenderLazyStateInit,
} from "./rules/state-and-effects.js";
import type { RulePlugin } from "./types.js";
} from "./rules/state-and-effects";
import type { RulePlugin } from "./types";

const plugin: RulePlugin = {
meta: { name: "react-doctor" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
GENERIC_EVENT_SUFFIXES,
GIANT_COMPONENT_LINE_THRESHOLD,
RENDER_FUNCTION_PATTERN,
} from "../constants.js";
import { isComponentAssignment, isComponentDeclaration, isUppercaseName } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../constants";
import { isComponentAssignment, isComponentDeclaration, isUppercaseName } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

export const noGenericHandlerNames: Rule = {
create: (context: RuleContext) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BARREL_INDEX_SUFFIXES, HEAVY_LIBRARIES } from "../constants.js";
import { findJsxAttribute, hasJsxAttribute } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
import { BARREL_INDEX_SUFFIXES, HEAVY_LIBRARIES } from "../constants";
import { findJsxAttribute, hasJsxAttribute } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

export const noBarrelImport: Rule = {
create: (context: RuleContext) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PASSIVE_EVENT_NAMES } from "../constants.js";
import { isMemberProperty } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
import { PASSIVE_EVENT_NAMES } from "../constants";
import { isMemberProperty } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

export const clientPassiveEventListeners: Rule = {
create: (context: RuleContext) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { INDEX_PARAMETER_NAMES } from "../constants.js";
import { findJsxAttribute, walkAst } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
import { INDEX_PARAMETER_NAMES } from "../constants";
import { findJsxAttribute, walkAst } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

const extractIndexName = (node: EsTreeNode): string | null => {
if (node.type === "Identifier" && INDEX_PARAMETER_NAMES.has(node.name)) return node.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
TINY_TEXT_THRESHOLD_PX,
WIDE_TRACKING_THRESHOLD_EM,
Z_INDEX_ABSURD_THRESHOLD,
} from "../constants.js";
import { findJsxAttribute, walkAst } from "../helpers.js";
import type { EsTreeNode, ParsedRgb, Rule, RuleContext } from "../types.js";
} from "../constants";
import { findJsxAttribute, walkAst } from "../helpers";
import type { EsTreeNode, ParsedRgb, Rule, RuleContext } from "../types";

const isOvershootCubicBezier = (value: string): boolean => {
const match = value.match(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
SEQUENTIAL_AWAIT_THRESHOLD,
STORAGE_OBJECTS,
TEST_FILE_PATTERN,
} from "../constants.js";
import { createLoopAwareVisitors, isMemberProperty, walkAst } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../constants";
import { createLoopAwareVisitors, isMemberProperty, walkAst } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

export const jsCombineIterations: Rule = {
create: (context: RuleContext) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
PAGES_DIRECTORY_PATTERN,
POLYFILL_SCRIPT_PATTERN,
ROUTE_HANDLER_FILE_PATTERN,
} from "../constants.js";
} from "../constants";
import {
containsFetchCall,
findJsxAttribute,
Expand All @@ -25,8 +25,8 @@ import {
isMemberProperty,
isUppercaseName,
walkAst,
} from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

export const nextjsNoImgElement: Rule = {
create: (context: RuleContext) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
LOADING_STATE_PATTERN,
MOTION_ANIMATE_PROPS,
SCRIPT_LOADING_ATTRIBUTES,
} from "../constants.js";
} from "../constants";
import {
getEffectCallback,
isComponentAssignment,
Expand All @@ -18,8 +18,8 @@ import {
isSimpleExpression,
isUppercaseName,
walkAst,
} from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

const isMemoCall = (node: EsTreeNode): boolean => {
if (node.type !== "CallExpression") return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
REACT_NATIVE_LIST_COMPONENTS,
REACT_NATIVE_TEXT_COMPONENTS,
REACT_NATIVE_TEXT_COMPONENT_KEYWORDS,
} from "../constants.js";
import { hasDirective, isMemberProperty } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../constants";
import { hasDirective, isMemberProperty } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

const resolveJsxElementName = (openingElement: EsTreeNode): string | null => {
const elementName = openingElement?.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
SECRET_MIN_LENGTH_CHARS,
SECRET_PATTERNS,
SECRET_VARIABLE_PATTERN,
} from "../constants.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../constants";
import type { EsTreeNode, Rule, RuleContext } from "../types";

export const noEval: Rule = {
create: (context: RuleContext) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
AUTH_FUNCTION_NAMES,
SERVER_ACTION_DIRECTORY_PATTERN,
SERVER_ACTION_FILE_PATTERN,
} from "../constants.js";
import { hasDirective, hasUseServerDirective, walkAst } from "../helpers.js";
import type { EsTreeNode, Rule, RuleContext } from "../types.js";
} from "../constants";
import { hasDirective, hasUseServerDirective, walkAst } from "../helpers";
import type { EsTreeNode, Rule, RuleContext } from "../types";

const containsAuthCheck = (statements: EsTreeNode[]): boolean => {
let foundAuthCall = false;
Expand Down
Loading
Loading