react-doctor 0.8.0-dev.c0d9d38 → 0.8.0-dev.eb2d687
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +30 -6
- package/dist/index.d.ts +88 -84
- package/dist/index.js +27 -3
- package/dist/lsp.js +27 -3
- package/dist/skills/react-doctor/references/explain.md +1 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cfa479fc-64e6-5081-86f2-1cbef233c1a0")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -38359,6 +38359,9 @@ const SCRIPT_EXTENSION_FRAGMENT = "[cm]?[jt]sx?";
|
|
|
38359
38359
|
const STORY_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:stories|story)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
38360
38360
|
const TEST_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:test|spec|fixture|fixtures)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
38361
38361
|
const TEST_FILE_DIRECTORY_PATTERN = /(?:^|\/)(?:__tests__|__test__|tests|test|__mocks__|cypress|e2e|playwright)\//;
|
|
38362
|
+
const AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN = /(?:^|\/)(?:tests|test)\//;
|
|
38363
|
+
const NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN = /\/(?:default|error|forbidden|global-error|layout|loading|not-found|page|route|template|unauthorized)\.[cm]?[jt]sx?$/;
|
|
38364
|
+
const SCRIPT_FILE_SUFFIX_PATTERN = /\.[cm]?[jt]sx?$/;
|
|
38362
38365
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
38363
38366
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
38364
38367
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -38369,6 +38372,14 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
38369
38372
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
38370
38373
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
38371
38374
|
};
|
|
38375
|
+
const isFrameworkRouteBeforeAmbiguousTestDirectory = (relativePath) => {
|
|
38376
|
+
const testDirectoryMatch = AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN.exec(relativePath);
|
|
38377
|
+
if (testDirectoryMatch === null) return false;
|
|
38378
|
+
let nearestRouteRoot;
|
|
38379
|
+
for (const pathSegment of relativePath.slice(0, testDirectoryMatch.index).split("/")) if (pathSegment === "app" || pathSegment === "pages") nearestRouteRoot = pathSegment;
|
|
38380
|
+
if (nearestRouteRoot === void 0) return false;
|
|
38381
|
+
return nearestRouteRoot === "pages" ? SCRIPT_FILE_SUFFIX_PATTERN.test(relativePath) : NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN.test(relativePath);
|
|
38382
|
+
};
|
|
38372
38383
|
/**
|
|
38373
38384
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
38374
38385
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -38385,6 +38396,7 @@ const classifyFileContext = (relativePath) => {
|
|
|
38385
38396
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
38386
38397
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
38387
38398
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
38399
|
+
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
38388
38400
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
38389
38401
|
};
|
|
38390
38402
|
/**
|
|
@@ -39244,6 +39256,7 @@ const VALID_RULE_SEVERITIES = [
|
|
|
39244
39256
|
"warn",
|
|
39245
39257
|
"off"
|
|
39246
39258
|
];
|
|
39259
|
+
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
39247
39260
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
39248
39261
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
39249
39262
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -39278,6 +39291,7 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
39278
39291
|
"includeRules",
|
|
39279
39292
|
"excludeRules"
|
|
39280
39293
|
];
|
|
39294
|
+
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
39281
39295
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
39282
39296
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39283
39297
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -39312,6 +39326,15 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
39312
39326
|
return;
|
|
39313
39327
|
}
|
|
39314
39328
|
const validatedSurfaceControls = {};
|
|
39329
|
+
if (rawControls.includeFileContexts !== void 0) {
|
|
39330
|
+
const qualifiedName = `surfaces.${surface}.includeFileContexts`;
|
|
39331
|
+
const result = validateStringArrayField(qualifiedName, rawControls.includeFileContexts);
|
|
39332
|
+
if (result !== void 0) validatedSurfaceControls.includeFileContexts = result.filter((fileContext) => {
|
|
39333
|
+
if (isIncludedFileContext(fileContext)) return true;
|
|
39334
|
+
warnConfigIssue(`config field "${qualifiedName}" lists "${fileContext}", which is not a non-production file context (expected one of: ${VALID_INCLUDED_FILE_CONTEXTS.join(", ")}); ignoring the entry.`);
|
|
39335
|
+
return false;
|
|
39336
|
+
});
|
|
39337
|
+
}
|
|
39315
39338
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
39316
39339
|
if (rawControls[fieldName] === void 0) continue;
|
|
39317
39340
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -42047,6 +42070,7 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
42047
42070
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
42048
42071
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
42049
42072
|
return {
|
|
42073
|
+
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
42050
42074
|
includeTags,
|
|
42051
42075
|
excludeTags,
|
|
42052
42076
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -42062,7 +42086,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
42062
42086
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
42063
42087
|
if (resolved.includeCategories.has(category)) return true;
|
|
42064
42088
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
42065
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
42089
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure") && !resolved.includeFileContexts.has(diagnostic.fileContext)) return false;
|
|
42066
42090
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
42067
42091
|
if (resolved.excludeCategories.has(category)) return false;
|
|
42068
42092
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -47223,7 +47247,7 @@ const makeNoopConsole = () => ({
|
|
|
47223
47247
|
});
|
|
47224
47248
|
//#endregion
|
|
47225
47249
|
//#region src/cli/utils/version.ts
|
|
47226
|
-
const VERSION = "0.8.0-dev.
|
|
47250
|
+
const VERSION = "0.8.0-dev.eb2d687";
|
|
47227
47251
|
//#endregion
|
|
47228
47252
|
//#region src/cli/utils/json-mode.ts
|
|
47229
47253
|
let context = null;
|
|
@@ -47575,13 +47599,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47575
47599
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47576
47600
|
* standard `SENTRY_RELEASE` override.
|
|
47577
47601
|
*/
|
|
47578
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.0-dev.
|
|
47602
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.0-dev.eb2d687`;
|
|
47579
47603
|
/**
|
|
47580
47604
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47581
47605
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47582
47606
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47583
47607
|
*/
|
|
47584
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.0-dev.
|
|
47608
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.0-dev.eb2d687") ? "development" : "production");
|
|
47585
47609
|
/**
|
|
47586
47610
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47587
47611
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -65867,4 +65891,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65867
65891
|
export {};
|
|
65868
65892
|
|
|
65869
65893
|
//# sourceMappingURL=cli.js.map
|
|
65870
|
-
//# debugId=
|
|
65894
|
+
//# debugId=cfa479fc-64e6-5081-86f2-1cbef233c1a0
|
package/dist/index.d.ts
CHANGED
|
@@ -8980,6 +8980,83 @@ declare namespace Annotations {
|
|
|
8980
8980
|
}
|
|
8981
8981
|
//#endregion
|
|
8982
8982
|
//#region ../core/dist/index.d.ts
|
|
8983
|
+
/**
|
|
8984
|
+
* A secondary source location attached to a diagnostic (oxlint's
|
|
8985
|
+
* non-primary labels). Editors render these as
|
|
8986
|
+
* `Diagnostic.relatedInformation` so a user can jump from, say, a
|
|
8987
|
+
* `no-derived-state` finding to the originating prop declaration.
|
|
8988
|
+
* Positions mirror `Diagnostic` semantics: `line` / `column` are
|
|
8989
|
+
* 1-indexed, and `offset` / `length` are UTF-8 byte spans from oxlint
|
|
8990
|
+
* when available (preferred for precise ranges).
|
|
8991
|
+
*/
|
|
8992
|
+
interface DiagnosticRelatedLocation {
|
|
8993
|
+
filePath: string;
|
|
8994
|
+
line: number;
|
|
8995
|
+
column: number;
|
|
8996
|
+
offset?: number;
|
|
8997
|
+
length?: number;
|
|
8998
|
+
endLine?: number;
|
|
8999
|
+
endColumn?: number;
|
|
9000
|
+
message: string;
|
|
9001
|
+
}
|
|
9002
|
+
type DiagnosticFileContext = "test" | "story" | "production";
|
|
9003
|
+
interface Diagnostic {
|
|
9004
|
+
filePath: string;
|
|
9005
|
+
plugin: string;
|
|
9006
|
+
rule: string;
|
|
9007
|
+
severity: "error" | "warning";
|
|
9008
|
+
title?: string;
|
|
9009
|
+
message: string;
|
|
9010
|
+
help: string;
|
|
9011
|
+
url?: string;
|
|
9012
|
+
line: number;
|
|
9013
|
+
column: number;
|
|
9014
|
+
/**
|
|
9015
|
+
* UTF-8 byte offset of the diagnostic's primary span start, straight
|
|
9016
|
+
* from oxlint's label span. Optional because environment / dead-code
|
|
9017
|
+
* diagnostics carry no source span. Editors (LSP) convert this into a
|
|
9018
|
+
* precise range via the in-memory document; non-editor consumers can
|
|
9019
|
+
* ignore it.
|
|
9020
|
+
*/
|
|
9021
|
+
offset?: number;
|
|
9022
|
+
/** UTF-8 byte length of the primary span (pairs with `offset`). */
|
|
9023
|
+
length?: number;
|
|
9024
|
+
/** 1-indexed end line of the primary span, when derivable. */
|
|
9025
|
+
endLine?: number;
|
|
9026
|
+
/** 1-indexed end column of the primary span, when derivable. */
|
|
9027
|
+
endColumn?: number;
|
|
9028
|
+
category: string;
|
|
9029
|
+
/**
|
|
9030
|
+
* Set when the finding's identity is the flagged element itself (a missing
|
|
9031
|
+
* attribute, a wrong element) rather than the flagged line's text — every
|
|
9032
|
+
* Accessibility-category finding, plus rules that opt in via their
|
|
9033
|
+
* `matchByOccurrence` metadata flag. `computeDiagnosticDelta` matches these
|
|
9034
|
+
* by `(filePath, plugin/rule)` occurrence count, so reformatting the flagged
|
|
9035
|
+
* line doesn't reclassify a pre-existing finding as new. Resolved at
|
|
9036
|
+
* diagnostic creation, where rule metadata is available. Absent means
|
|
9037
|
+
* line-text-sensitive matching (the default).
|
|
9038
|
+
*/
|
|
9039
|
+
matchByOccurrence?: boolean;
|
|
9040
|
+
/**
|
|
9041
|
+
* Set when the file never ships to users (`"test"` / `"story"`), so
|
|
9042
|
+
* renderers can label the site instead of implying production impact.
|
|
9043
|
+
* Omitted for production files (the default).
|
|
9044
|
+
*/
|
|
9045
|
+
fileContext?: Exclude<DiagnosticFileContext, "production">;
|
|
9046
|
+
suppressionHint?: string;
|
|
9047
|
+
/** Secondary source locations (oxlint's non-primary labels). */
|
|
9048
|
+
relatedLocations?: DiagnosticRelatedLocation[];
|
|
9049
|
+
/**
|
|
9050
|
+
* Stable id shared by every finding that a single fix resolves together —
|
|
9051
|
+
* e.g. four `useEffect`s that reset state on one prop change all clear with
|
|
9052
|
+
* one `key` prop. Set only when ≥2 findings share a root cause; absent for
|
|
9053
|
+
* standalone findings. A consumer that turns findings into work items should
|
|
9054
|
+
* group by it so one fix reads as one task, not N. Presentation-only and
|
|
9055
|
+
* score-neutral — the score never reads it.
|
|
9056
|
+
*/
|
|
9057
|
+
fixGroupId?: string;
|
|
9058
|
+
}
|
|
9059
|
+
//#endregion
|
|
8983
9060
|
//#region src/types/config.d.ts
|
|
8984
9061
|
/**
|
|
8985
9062
|
* Severity threshold at which a scan blocks CI (exits non-zero). Controlled
|
|
@@ -9057,9 +9134,9 @@ interface ReactDoctorIgnoreConfig {
|
|
|
9057
9134
|
* Defaults: design rules (tag `"design"`) are excluded from `prComment`,
|
|
9058
9135
|
* `score`, and `ciFailure` so style cleanup doesn't dilute meaningful
|
|
9059
9136
|
* React findings. Diagnostics stamped with `fileContext: "test" | "story"`
|
|
9060
|
-
* are excluded from `score` and `ciFailure`. Both remain in `cli
|
|
9061
|
-
*
|
|
9062
|
-
*
|
|
9137
|
+
* are excluded from `score` and `ciFailure`. Both remain in `cli`. Explicit
|
|
9138
|
+
* rule, category, or tag includes promote matching diagnostics, while
|
|
9139
|
+
* `includeFileContexts` restores a context without bypassing other exclusions.
|
|
9063
9140
|
*/
|
|
9064
9141
|
type DiagnosticSurface = "cli" | "prComment" | "score" | "ciFailure";
|
|
9065
9142
|
/**
|
|
@@ -9089,6 +9166,11 @@ interface SeverityBuckets {
|
|
|
9089
9166
|
"compiler-cleanup"?: RuleSeverityOverride;
|
|
9090
9167
|
}
|
|
9091
9168
|
interface SurfaceControls {
|
|
9169
|
+
/**
|
|
9170
|
+
* Non-production file contexts to retain on this surface. This restores
|
|
9171
|
+
* test or story findings without overriding unrelated tag exclusions.
|
|
9172
|
+
*/
|
|
9173
|
+
includeFileContexts?: Array<Exclude<DiagnosticFileContext, "production">>;
|
|
9092
9174
|
/**
|
|
9093
9175
|
* Tag names whose diagnostics should be force-included on the surface,
|
|
9094
9176
|
* even if a default or category-level exclusion would otherwise drop
|
|
@@ -9353,9 +9435,9 @@ interface ReactDoctorConfig {
|
|
|
9353
9435
|
* - `score` and `ciFailure` exclude test/story file contexts unless explicitly included
|
|
9354
9436
|
*
|
|
9355
9437
|
* Pass any controls block, including an empty `{}`, to keep the defaults.
|
|
9356
|
-
* Your include/exclude entries layer on top.
|
|
9357
|
-
*
|
|
9358
|
-
*
|
|
9438
|
+
* Your include/exclude entries layer on top. Rule, category, and tag includes
|
|
9439
|
+
* win over their exclusions. Included file contexts still respect unrelated
|
|
9440
|
+
* exclusions, so restoring story findings does not restore every design rule.
|
|
9359
9441
|
*/
|
|
9360
9442
|
surfaces?: Partial<Record<DiagnosticSurface, SurfaceControls>>;
|
|
9361
9443
|
/**
|
|
@@ -9449,84 +9531,6 @@ interface ReactDoctorConfig {
|
|
|
9449
9531
|
*/
|
|
9450
9532
|
plugins?: string[];
|
|
9451
9533
|
} //#endregion
|
|
9452
|
-
//#region src/types/diagnostic.d.ts
|
|
9453
|
-
/**
|
|
9454
|
-
* A secondary source location attached to a diagnostic (oxlint's
|
|
9455
|
-
* non-primary labels). Editors render these as
|
|
9456
|
-
* `Diagnostic.relatedInformation` so a user can jump from, say, a
|
|
9457
|
-
* `no-derived-state` finding to the originating prop declaration.
|
|
9458
|
-
* Positions mirror `Diagnostic` semantics: `line` / `column` are
|
|
9459
|
-
* 1-indexed, and `offset` / `length` are UTF-8 byte spans from oxlint
|
|
9460
|
-
* when available (preferred for precise ranges).
|
|
9461
|
-
*/
|
|
9462
|
-
interface DiagnosticRelatedLocation {
|
|
9463
|
-
filePath: string;
|
|
9464
|
-
line: number;
|
|
9465
|
-
column: number;
|
|
9466
|
-
offset?: number;
|
|
9467
|
-
length?: number;
|
|
9468
|
-
endLine?: number;
|
|
9469
|
-
endColumn?: number;
|
|
9470
|
-
message: string;
|
|
9471
|
-
}
|
|
9472
|
-
type DiagnosticFileContext = "test" | "story" | "production";
|
|
9473
|
-
interface Diagnostic {
|
|
9474
|
-
filePath: string;
|
|
9475
|
-
plugin: string;
|
|
9476
|
-
rule: string;
|
|
9477
|
-
severity: "error" | "warning";
|
|
9478
|
-
title?: string;
|
|
9479
|
-
message: string;
|
|
9480
|
-
help: string;
|
|
9481
|
-
url?: string;
|
|
9482
|
-
line: number;
|
|
9483
|
-
column: number;
|
|
9484
|
-
/**
|
|
9485
|
-
* UTF-8 byte offset of the diagnostic's primary span start, straight
|
|
9486
|
-
* from oxlint's label span. Optional because environment / dead-code
|
|
9487
|
-
* diagnostics carry no source span. Editors (LSP) convert this into a
|
|
9488
|
-
* precise range via the in-memory document; non-editor consumers can
|
|
9489
|
-
* ignore it.
|
|
9490
|
-
*/
|
|
9491
|
-
offset?: number;
|
|
9492
|
-
/** UTF-8 byte length of the primary span (pairs with `offset`). */
|
|
9493
|
-
length?: number;
|
|
9494
|
-
/** 1-indexed end line of the primary span, when derivable. */
|
|
9495
|
-
endLine?: number;
|
|
9496
|
-
/** 1-indexed end column of the primary span, when derivable. */
|
|
9497
|
-
endColumn?: number;
|
|
9498
|
-
category: string;
|
|
9499
|
-
/**
|
|
9500
|
-
* Set when the finding's identity is the flagged element itself (a missing
|
|
9501
|
-
* attribute, a wrong element) rather than the flagged line's text — every
|
|
9502
|
-
* Accessibility-category finding, plus rules that opt in via their
|
|
9503
|
-
* `matchByOccurrence` metadata flag. `computeDiagnosticDelta` matches these
|
|
9504
|
-
* by `(filePath, plugin/rule)` occurrence count, so reformatting the flagged
|
|
9505
|
-
* line doesn't reclassify a pre-existing finding as new. Resolved at
|
|
9506
|
-
* diagnostic creation, where rule metadata is available. Absent means
|
|
9507
|
-
* line-text-sensitive matching (the default).
|
|
9508
|
-
*/
|
|
9509
|
-
matchByOccurrence?: boolean;
|
|
9510
|
-
/**
|
|
9511
|
-
* Set when the file never ships to users (`"test"` / `"story"`), so
|
|
9512
|
-
* renderers can label the site instead of implying production impact.
|
|
9513
|
-
* Omitted for production files (the default).
|
|
9514
|
-
*/
|
|
9515
|
-
fileContext?: Exclude<DiagnosticFileContext, "production">;
|
|
9516
|
-
suppressionHint?: string;
|
|
9517
|
-
/** Secondary source locations (oxlint's non-primary labels). */
|
|
9518
|
-
relatedLocations?: DiagnosticRelatedLocation[];
|
|
9519
|
-
/**
|
|
9520
|
-
* Stable id shared by every finding that a single fix resolves together —
|
|
9521
|
-
* e.g. four `useEffect`s that reset state on one prop change all clear with
|
|
9522
|
-
* one `key` prop. Set only when ≥2 findings share a root cause; absent for
|
|
9523
|
-
* standalone findings. A consumer that turns findings into work items should
|
|
9524
|
-
* group by it so one fix reads as one task, not N. Presentation-only and
|
|
9525
|
-
* score-neutral — the score never reads it.
|
|
9526
|
-
*/
|
|
9527
|
-
fixGroupId?: string;
|
|
9528
|
-
}
|
|
9529
|
-
//#endregion
|
|
9530
9534
|
//#region src/types/project-info.d.ts
|
|
9531
9535
|
type Framework = FrameworkToken;
|
|
9532
9536
|
interface ProjectInfo {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4a473a3e-0567-5c2a-8eea-947cdfc7b9ee")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
@@ -35155,6 +35155,9 @@ const SCRIPT_EXTENSION_FRAGMENT = "[cm]?[jt]sx?";
|
|
|
35155
35155
|
const STORY_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:stories|story)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
35156
35156
|
const TEST_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:test|spec|fixture|fixtures)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
35157
35157
|
const TEST_FILE_DIRECTORY_PATTERN = /(?:^|\/)(?:__tests__|__test__|tests|test|__mocks__|cypress|e2e|playwright)\//;
|
|
35158
|
+
const AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN = /(?:^|\/)(?:tests|test)\//;
|
|
35159
|
+
const NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN = /\/(?:default|error|forbidden|global-error|layout|loading|not-found|page|route|template|unauthorized)\.[cm]?[jt]sx?$/;
|
|
35160
|
+
const SCRIPT_FILE_SUFFIX_PATTERN = /\.[cm]?[jt]sx?$/;
|
|
35158
35161
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
35159
35162
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
35160
35163
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -35165,6 +35168,14 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
35165
35168
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
35166
35169
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
35167
35170
|
};
|
|
35171
|
+
const isFrameworkRouteBeforeAmbiguousTestDirectory = (relativePath) => {
|
|
35172
|
+
const testDirectoryMatch = AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN.exec(relativePath);
|
|
35173
|
+
if (testDirectoryMatch === null) return false;
|
|
35174
|
+
let nearestRouteRoot;
|
|
35175
|
+
for (const pathSegment of relativePath.slice(0, testDirectoryMatch.index).split("/")) if (pathSegment === "app" || pathSegment === "pages") nearestRouteRoot = pathSegment;
|
|
35176
|
+
if (nearestRouteRoot === void 0) return false;
|
|
35177
|
+
return nearestRouteRoot === "pages" ? SCRIPT_FILE_SUFFIX_PATTERN.test(relativePath) : NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN.test(relativePath);
|
|
35178
|
+
};
|
|
35168
35179
|
/**
|
|
35169
35180
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
35170
35181
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -35181,6 +35192,7 @@ const classifyFileContext = (relativePath) => {
|
|
|
35181
35192
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
35182
35193
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
35183
35194
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
35195
|
+
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
35184
35196
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
35185
35197
|
};
|
|
35186
35198
|
/**
|
|
@@ -36044,6 +36056,7 @@ const VALID_RULE_SEVERITIES = [
|
|
|
36044
36056
|
"warn",
|
|
36045
36057
|
"off"
|
|
36046
36058
|
];
|
|
36059
|
+
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
36047
36060
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
36048
36061
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
36049
36062
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -36078,6 +36091,7 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
36078
36091
|
"includeRules",
|
|
36079
36092
|
"excludeRules"
|
|
36080
36093
|
];
|
|
36094
|
+
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
36081
36095
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
36082
36096
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36083
36097
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -36112,6 +36126,15 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
36112
36126
|
return;
|
|
36113
36127
|
}
|
|
36114
36128
|
const validatedSurfaceControls = {};
|
|
36129
|
+
if (rawControls.includeFileContexts !== void 0) {
|
|
36130
|
+
const qualifiedName = `surfaces.${surface}.includeFileContexts`;
|
|
36131
|
+
const result = validateStringArrayField(qualifiedName, rawControls.includeFileContexts);
|
|
36132
|
+
if (result !== void 0) validatedSurfaceControls.includeFileContexts = result.filter((fileContext) => {
|
|
36133
|
+
if (isIncludedFileContext(fileContext)) return true;
|
|
36134
|
+
warnConfigIssue(`config field "${qualifiedName}" lists "${fileContext}", which is not a non-production file context (expected one of: ${VALID_INCLUDED_FILE_CONTEXTS.join(", ")}); ignoring the entry.`);
|
|
36135
|
+
return false;
|
|
36136
|
+
});
|
|
36137
|
+
}
|
|
36115
36138
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
36116
36139
|
if (rawControls[fieldName] === void 0) continue;
|
|
36117
36140
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -38803,6 +38826,7 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
38803
38826
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
38804
38827
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
38805
38828
|
return {
|
|
38829
|
+
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
38806
38830
|
includeTags,
|
|
38807
38831
|
excludeTags,
|
|
38808
38832
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -38818,7 +38842,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
38818
38842
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
38819
38843
|
if (resolved.includeCategories.has(category)) return true;
|
|
38820
38844
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
38821
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
38845
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure") && !resolved.includeFileContexts.has(diagnostic.fileContext)) return false;
|
|
38822
38846
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
38823
38847
|
if (resolved.excludeCategories.has(category)) return false;
|
|
38824
38848
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -43727,4 +43751,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
43727
43751
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
43728
43752
|
|
|
43729
43753
|
//# sourceMappingURL=index.js.map
|
|
43730
|
-
//# debugId=
|
|
43754
|
+
//# debugId=4a473a3e-0567-5c2a-8eea-947cdfc7b9ee
|
package/dist/lsp.js
CHANGED
|
@@ -35207,6 +35207,9 @@ const SCRIPT_EXTENSION_FRAGMENT = "[cm]?[jt]sx?";
|
|
|
35207
35207
|
const STORY_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:stories|story)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
35208
35208
|
const TEST_FILE_SUFFIX_PATTERN = new RegExp(`\\.(?:test|spec|fixture|fixtures)\\.(?:${SCRIPT_EXTENSION_FRAGMENT})$`);
|
|
35209
35209
|
const TEST_FILE_DIRECTORY_PATTERN = /(?:^|\/)(?:__tests__|__test__|tests|test|__mocks__|cypress|e2e|playwright)\//;
|
|
35210
|
+
const AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN = /(?:^|\/)(?:tests|test)\//;
|
|
35211
|
+
const NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN = /\/(?:default|error|forbidden|global-error|layout|loading|not-found|page|route|template|unauthorized)\.[cm]?[jt]sx?$/;
|
|
35212
|
+
const SCRIPT_FILE_SUFFIX_PATTERN = /\.[cm]?[jt]sx?$/;
|
|
35210
35213
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
35211
35214
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
35212
35215
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -35217,6 +35220,14 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
35217
35220
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
35218
35221
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
35219
35222
|
};
|
|
35223
|
+
const isFrameworkRouteBeforeAmbiguousTestDirectory = (relativePath) => {
|
|
35224
|
+
const testDirectoryMatch = AMBIGUOUS_TEST_ROUTE_DIRECTORY_PATTERN.exec(relativePath);
|
|
35225
|
+
if (testDirectoryMatch === null) return false;
|
|
35226
|
+
let nearestRouteRoot;
|
|
35227
|
+
for (const pathSegment of relativePath.slice(0, testDirectoryMatch.index).split("/")) if (pathSegment === "app" || pathSegment === "pages") nearestRouteRoot = pathSegment;
|
|
35228
|
+
if (nearestRouteRoot === void 0) return false;
|
|
35229
|
+
return nearestRouteRoot === "pages" ? SCRIPT_FILE_SUFFIX_PATTERN.test(relativePath) : NEXT_APP_ROUTE_FILE_SUFFIX_PATTERN.test(relativePath);
|
|
35230
|
+
};
|
|
35220
35231
|
/**
|
|
35221
35232
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
35222
35233
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -35233,6 +35244,7 @@ const classifyFileContext = (relativePath) => {
|
|
|
35233
35244
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
35234
35245
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
35235
35246
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
35247
|
+
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
35236
35248
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
35237
35249
|
};
|
|
35238
35250
|
/**
|
|
@@ -36070,6 +36082,7 @@ const VALID_RULE_SEVERITIES = [
|
|
|
36070
36082
|
"warn",
|
|
36071
36083
|
"off"
|
|
36072
36084
|
];
|
|
36085
|
+
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
36073
36086
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
36074
36087
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
36075
36088
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -36104,6 +36117,7 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
36104
36117
|
"includeRules",
|
|
36105
36118
|
"excludeRules"
|
|
36106
36119
|
];
|
|
36120
|
+
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
36107
36121
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
36108
36122
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36109
36123
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -36138,6 +36152,15 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
36138
36152
|
return;
|
|
36139
36153
|
}
|
|
36140
36154
|
const validatedSurfaceControls = {};
|
|
36155
|
+
if (rawControls.includeFileContexts !== void 0) {
|
|
36156
|
+
const qualifiedName = `surfaces.${surface}.includeFileContexts`;
|
|
36157
|
+
const result = validateStringArrayField(qualifiedName, rawControls.includeFileContexts);
|
|
36158
|
+
if (result !== void 0) validatedSurfaceControls.includeFileContexts = result.filter((fileContext) => {
|
|
36159
|
+
if (isIncludedFileContext(fileContext)) return true;
|
|
36160
|
+
warnConfigIssue(`config field "${qualifiedName}" lists "${fileContext}", which is not a non-production file context (expected one of: ${VALID_INCLUDED_FILE_CONTEXTS.join(", ")}); ignoring the entry.`);
|
|
36161
|
+
return false;
|
|
36162
|
+
});
|
|
36163
|
+
}
|
|
36141
36164
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
36142
36165
|
if (rawControls[fieldName] === void 0) continue;
|
|
36143
36166
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -38781,6 +38804,7 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
38781
38804
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
38782
38805
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
38783
38806
|
return {
|
|
38807
|
+
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
38784
38808
|
includeTags,
|
|
38785
38809
|
excludeTags,
|
|
38786
38810
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -38796,7 +38820,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
38796
38820
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
38797
38821
|
if (resolved.includeCategories.has(category)) return true;
|
|
38798
38822
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
38799
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
38823
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure") && !resolved.includeFileContexts.has(diagnostic.fileContext)) return false;
|
|
38800
38824
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
38801
38825
|
if (resolved.excludeCategories.has(category)) return false;
|
|
38802
38826
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -45347,5 +45371,5 @@ const startLanguageServer = () => {
|
|
|
45347
45371
|
};
|
|
45348
45372
|
//#endregion
|
|
45349
45373
|
export { startLanguageServer };
|
|
45350
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
45351
|
-
//# debugId=
|
|
45374
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2dde2cd1-f390-5e9f-8370-937973c22f36")}catch(e){}}();
|
|
45375
|
+
//# debugId=2dde2cd1-f390-5e9f-8370-937973c22f36
|
|
@@ -51,6 +51,7 @@ Match the control to the intent — prefer the narrowest one:
|
|
|
51
51
|
- **A whole area is unwanted** (e.g. all React Native rules) → `rules category "<Category>" off`.
|
|
52
52
|
- **A behavioral family is noisy** (`design`, `test-noise`, `migration-hint`) → `rules ignore-tag <tag>`.
|
|
53
53
|
- **Keep it locally but hide from PR comment / score / CI gate only** → do NOT disable. Edit `surfaces` in your config (`surfaces.prComment.excludeRules`, `surfaces.score.excludeTags`, `surfaces.ciFailure.excludeCategories`). The rule still shows in local `cli` output.
|
|
54
|
+
- **Restore test or story findings to production health** → set `surfaces.score.includeFileContexts` or `surfaces.ciFailure.includeFileContexts` to `["test"]`, `["story"]`, or both. Other surface exclusions still apply.
|
|
54
55
|
|
|
55
56
|
How the layers combine: `ignore.tags` disables every rule carrying that tag **before** linting, so a tagged rule stays off even if `rules`/`categories` set it to `warn`/`error` (a rule-level override cannot re-enable a tag-ignored rule). For rules that aren't tag-disabled, `rules` overrides `categories` overrides the rule's default. `surfaces` is visibility-only and never changes whether a rule runs.
|
|
56
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.8.0-dev.
|
|
3
|
+
"version": "0.8.0-dev.eb2d687",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
68
|
"deslop-js": "0.8.0",
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.8.0-dev.
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.8.0-dev.eb2d687"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"@xterm/headless": "^6.0.0",
|
|
75
75
|
"commander": "^14.0.3",
|
|
76
76
|
"ora": "^9.4.0",
|
|
77
|
-
"@react-doctor/core": "0.8.0",
|
|
78
77
|
"@react-doctor/api": "0.8.0",
|
|
78
|
+
"@react-doctor/core": "0.8.0",
|
|
79
79
|
"@react-doctor/language-server": "0.8.0"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|