react-doctor 0.8.0-dev.eb2d687 → 0.8.0
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 +6 -30
- package/dist/index.d.ts +84 -88
- package/dist/index.js +3 -27
- package/dist/lsp.js +3 -27
- package/dist/skills/react-doctor/references/explain.md +0 -1
- package/package.json +2 -2
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]="5cb8a881-e8fc-5561-8b89-ad2c8357acbc")}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,9 +38359,6 @@ 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?$/;
|
|
38365
38362
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
38366
38363
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
38367
38364
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -38372,14 +38369,6 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
38372
38369
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
38373
38370
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
38374
38371
|
};
|
|
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
|
-
};
|
|
38383
38372
|
/**
|
|
38384
38373
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
38385
38374
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -38396,7 +38385,6 @@ const classifyFileContext = (relativePath) => {
|
|
|
38396
38385
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
38397
38386
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
38398
38387
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
38399
|
-
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
38400
38388
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
38401
38389
|
};
|
|
38402
38390
|
/**
|
|
@@ -39256,7 +39244,6 @@ const VALID_RULE_SEVERITIES = [
|
|
|
39256
39244
|
"warn",
|
|
39257
39245
|
"off"
|
|
39258
39246
|
];
|
|
39259
|
-
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
39260
39247
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
39261
39248
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
39262
39249
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -39291,7 +39278,6 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
39291
39278
|
"includeRules",
|
|
39292
39279
|
"excludeRules"
|
|
39293
39280
|
];
|
|
39294
|
-
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
39295
39281
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
39296
39282
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39297
39283
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -39326,15 +39312,6 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
39326
39312
|
return;
|
|
39327
39313
|
}
|
|
39328
39314
|
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
|
-
}
|
|
39338
39315
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
39339
39316
|
if (rawControls[fieldName] === void 0) continue;
|
|
39340
39317
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -42070,7 +42047,6 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
42070
42047
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
42071
42048
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
42072
42049
|
return {
|
|
42073
|
-
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
42074
42050
|
includeTags,
|
|
42075
42051
|
excludeTags,
|
|
42076
42052
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -42086,7 +42062,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
42086
42062
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
42087
42063
|
if (resolved.includeCategories.has(category)) return true;
|
|
42088
42064
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
42089
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")
|
|
42065
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
42090
42066
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
42091
42067
|
if (resolved.excludeCategories.has(category)) return false;
|
|
42092
42068
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -47247,7 +47223,7 @@ const makeNoopConsole = () => ({
|
|
|
47247
47223
|
});
|
|
47248
47224
|
//#endregion
|
|
47249
47225
|
//#region src/cli/utils/version.ts
|
|
47250
|
-
const VERSION = "0.8.0
|
|
47226
|
+
const VERSION = "0.8.0";
|
|
47251
47227
|
//#endregion
|
|
47252
47228
|
//#region src/cli/utils/json-mode.ts
|
|
47253
47229
|
let context = null;
|
|
@@ -47599,13 +47575,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47599
47575
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47600
47576
|
* standard `SENTRY_RELEASE` override.
|
|
47601
47577
|
*/
|
|
47602
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.0
|
|
47578
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.0`;
|
|
47603
47579
|
/**
|
|
47604
47580
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47605
47581
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47606
47582
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47607
47583
|
*/
|
|
47608
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.0
|
|
47584
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.0") ? "development" : "production");
|
|
47609
47585
|
/**
|
|
47610
47586
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47611
47587
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -65891,4 +65867,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65891
65867
|
export {};
|
|
65892
65868
|
|
|
65893
65869
|
//# sourceMappingURL=cli.js.map
|
|
65894
|
-
//# debugId=
|
|
65870
|
+
//# debugId=5cb8a881-e8fc-5561-8b89-ad2c8357acbc
|
package/dist/index.d.ts
CHANGED
|
@@ -8980,83 +8980,6 @@ 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
|
|
9060
8983
|
//#region src/types/config.d.ts
|
|
9061
8984
|
/**
|
|
9062
8985
|
* Severity threshold at which a scan blocks CI (exits non-zero). Controlled
|
|
@@ -9134,9 +9057,9 @@ interface ReactDoctorIgnoreConfig {
|
|
|
9134
9057
|
* Defaults: design rules (tag `"design"`) are excluded from `prComment`,
|
|
9135
9058
|
* `score`, and `ciFailure` so style cleanup doesn't dilute meaningful
|
|
9136
9059
|
* React findings. Diagnostics stamped with `fileContext: "test" | "story"`
|
|
9137
|
-
* are excluded from `score` and `ciFailure`. Both remain in `cli
|
|
9138
|
-
* rule, category, or tag
|
|
9139
|
-
*
|
|
9060
|
+
* are excluded from `score` and `ciFailure`. Both remain in `cli`, and an
|
|
9061
|
+
* explicit rule, category, or tag include promotes a diagnostic back onto
|
|
9062
|
+
* the selected surface.
|
|
9140
9063
|
*/
|
|
9141
9064
|
type DiagnosticSurface = "cli" | "prComment" | "score" | "ciFailure";
|
|
9142
9065
|
/**
|
|
@@ -9166,11 +9089,6 @@ interface SeverityBuckets {
|
|
|
9166
9089
|
"compiler-cleanup"?: RuleSeverityOverride;
|
|
9167
9090
|
}
|
|
9168
9091
|
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">>;
|
|
9174
9092
|
/**
|
|
9175
9093
|
* Tag names whose diagnostics should be force-included on the surface,
|
|
9176
9094
|
* even if a default or category-level exclusion would otherwise drop
|
|
@@ -9435,9 +9353,9 @@ interface ReactDoctorConfig {
|
|
|
9435
9353
|
* - `score` and `ciFailure` exclude test/story file contexts unless explicitly included
|
|
9436
9354
|
*
|
|
9437
9355
|
* Pass any controls block, including an empty `{}`, to keep the defaults.
|
|
9438
|
-
* Your include/exclude entries layer on top.
|
|
9439
|
-
*
|
|
9440
|
-
*
|
|
9356
|
+
* Your include/exclude entries layer on top. Includes always win over
|
|
9357
|
+
* excludes. For example, you can promote one high-signal `design-*` rule
|
|
9358
|
+
* back into the score or PR-comment surface.
|
|
9441
9359
|
*/
|
|
9442
9360
|
surfaces?: Partial<Record<DiagnosticSurface, SurfaceControls>>;
|
|
9443
9361
|
/**
|
|
@@ -9531,6 +9449,84 @@ interface ReactDoctorConfig {
|
|
|
9531
9449
|
*/
|
|
9532
9450
|
plugins?: string[];
|
|
9533
9451
|
} //#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
|
|
9534
9530
|
//#region src/types/project-info.d.ts
|
|
9535
9531
|
type Framework = FrameworkToken;
|
|
9536
9532
|
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]="81e065f1-a43b-51c2-ac53-2f38390032ed")}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,9 +35155,6 @@ 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?$/;
|
|
35161
35158
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
35162
35159
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
35163
35160
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -35168,14 +35165,6 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
35168
35165
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
35169
35166
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
35170
35167
|
};
|
|
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
|
-
};
|
|
35179
35168
|
/**
|
|
35180
35169
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
35181
35170
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -35192,7 +35181,6 @@ const classifyFileContext = (relativePath) => {
|
|
|
35192
35181
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
35193
35182
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
35194
35183
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
35195
|
-
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
35196
35184
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
35197
35185
|
};
|
|
35198
35186
|
/**
|
|
@@ -36056,7 +36044,6 @@ const VALID_RULE_SEVERITIES = [
|
|
|
36056
36044
|
"warn",
|
|
36057
36045
|
"off"
|
|
36058
36046
|
];
|
|
36059
|
-
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
36060
36047
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
36061
36048
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
36062
36049
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -36091,7 +36078,6 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
36091
36078
|
"includeRules",
|
|
36092
36079
|
"excludeRules"
|
|
36093
36080
|
];
|
|
36094
|
-
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
36095
36081
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
36096
36082
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36097
36083
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -36126,15 +36112,6 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
36126
36112
|
return;
|
|
36127
36113
|
}
|
|
36128
36114
|
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
|
-
}
|
|
36138
36115
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
36139
36116
|
if (rawControls[fieldName] === void 0) continue;
|
|
36140
36117
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -38826,7 +38803,6 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
38826
38803
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
38827
38804
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
38828
38805
|
return {
|
|
38829
|
-
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
38830
38806
|
includeTags,
|
|
38831
38807
|
excludeTags,
|
|
38832
38808
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -38842,7 +38818,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
38842
38818
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
38843
38819
|
if (resolved.includeCategories.has(category)) return true;
|
|
38844
38820
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
38845
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")
|
|
38821
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
38846
38822
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
38847
38823
|
if (resolved.excludeCategories.has(category)) return false;
|
|
38848
38824
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -43751,4 +43727,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
43751
43727
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
43752
43728
|
|
|
43753
43729
|
//# sourceMappingURL=index.js.map
|
|
43754
|
-
//# debugId=
|
|
43730
|
+
//# debugId=81e065f1-a43b-51c2-ac53-2f38390032ed
|
package/dist/lsp.js
CHANGED
|
@@ -35207,9 +35207,6 @@ 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?$/;
|
|
35213
35210
|
const FIXTURE_PROJECT_PATTERN = /\/(?:fixtures|__fixtures__)\//;
|
|
35214
35211
|
const SOURCE_ROOT_PATTERN = /\/(?:src|app|lib|components|pages|features|modules|packages|apps|frontend|client)\//g;
|
|
35215
35212
|
const stripAboveSourceRoot = (relativePath) => {
|
|
@@ -35220,14 +35217,6 @@ const stripAboveSourceRoot = (relativePath) => {
|
|
|
35220
35217
|
if (lastIdx >= 0) return relativePath.slice(lastIdx);
|
|
35221
35218
|
return relativePath.slice(fixtureMatch.index + fixtureMatch[0].length - 1);
|
|
35222
35219
|
};
|
|
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
|
-
};
|
|
35231
35220
|
/**
|
|
35232
35221
|
* Classifies where a file sits relative to shipped code. A finding in a
|
|
35233
35222
|
* `.stories.tsx` or `.spec.ts` file never runs in front of users, so
|
|
@@ -35244,7 +35233,6 @@ const classifyFileContext = (relativePath) => {
|
|
|
35244
35233
|
if (STORY_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "story";
|
|
35245
35234
|
if (TEST_FILE_SUFFIX_PATTERN.test(forwardSlashed)) return "test";
|
|
35246
35235
|
const scoped = stripAboveSourceRoot(forwardSlashed);
|
|
35247
|
-
if (isFrameworkRouteBeforeAmbiguousTestDirectory(scoped)) return "production";
|
|
35248
35236
|
return TEST_FILE_DIRECTORY_PATTERN.test(scoped) ? "test" : "production";
|
|
35249
35237
|
};
|
|
35250
35238
|
/**
|
|
@@ -36082,7 +36070,6 @@ const VALID_RULE_SEVERITIES = [
|
|
|
36082
36070
|
"warn",
|
|
36083
36071
|
"off"
|
|
36084
36072
|
];
|
|
36085
|
-
const VALID_INCLUDED_FILE_CONTEXTS = ["test", "story"];
|
|
36086
36073
|
const KNOWN_CATEGORY_LABEL = DIAGNOSTIC_CATEGORY_BUCKETS.join(", ");
|
|
36087
36074
|
const isDiagnosticCategoryBucket = (value) => DIAGNOSTIC_CATEGORY_BUCKETS.includes(value);
|
|
36088
36075
|
const filterKnownCategories = (fieldName, categories) => categories.filter((category) => {
|
|
@@ -36117,7 +36104,6 @@ const SURFACE_CONTROL_FIELD_NAMES = [
|
|
|
36117
36104
|
"includeRules",
|
|
36118
36105
|
"excludeRules"
|
|
36119
36106
|
];
|
|
36120
|
-
const isIncludedFileContext = (value) => VALID_INCLUDED_FILE_CONTEXTS.some((fileContext) => fileContext === value);
|
|
36121
36107
|
const SEVERITY_FIELD_NAMES = ["rules", "categories"];
|
|
36122
36108
|
const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36123
36109
|
const formatType = (value) => typeof value === "string" ? `"${value}"` : typeof value;
|
|
@@ -36152,15 +36138,6 @@ const validateSurfaceControls = (surface, rawControls) => {
|
|
|
36152
36138
|
return;
|
|
36153
36139
|
}
|
|
36154
36140
|
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
|
-
}
|
|
36164
36141
|
for (const fieldName of SURFACE_CONTROL_FIELD_NAMES) {
|
|
36165
36142
|
if (rawControls[fieldName] === void 0) continue;
|
|
36166
36143
|
const qualifiedName = `surfaces.${surface}.${fieldName}`;
|
|
@@ -38804,7 +38781,6 @@ const buildResolvedControls = (surface, userControls) => {
|
|
|
38804
38781
|
for (const tag of includeTags) excludeTags.delete(tag);
|
|
38805
38782
|
for (const tag of toStringSet(userControls?.excludeTags)) excludeTags.add(tag);
|
|
38806
38783
|
return {
|
|
38807
|
-
includeFileContexts: toStringSet(userControls?.includeFileContexts),
|
|
38808
38784
|
includeTags,
|
|
38809
38785
|
excludeTags,
|
|
38810
38786
|
includeCategories: toStringSet(userControls?.includeCategories),
|
|
@@ -38820,7 +38796,7 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
38820
38796
|
if (resolved.includeRuleKeys.has(ruleKey)) return true;
|
|
38821
38797
|
if (resolved.includeCategories.has(category)) return true;
|
|
38822
38798
|
if (intersects(tags, resolved.includeTags)) return true;
|
|
38823
|
-
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")
|
|
38799
|
+
if (diagnostic.fileContext !== void 0 && (surface === "score" || surface === "ciFailure")) return false;
|
|
38824
38800
|
if (resolved.excludeRuleKeys.has(ruleKey)) return false;
|
|
38825
38801
|
if (resolved.excludeCategories.has(category)) return false;
|
|
38826
38802
|
if (intersects(tags, resolved.excludeTags)) return false;
|
|
@@ -45371,5 +45347,5 @@ const startLanguageServer = () => {
|
|
|
45371
45347
|
};
|
|
45372
45348
|
//#endregion
|
|
45373
45349
|
export { startLanguageServer };
|
|
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]="
|
|
45375
|
-
//# debugId=
|
|
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]="9eca6678-56ba-5032-b04c-b1ed965dffb4")}catch(e){}}();
|
|
45351
|
+
//# debugId=9eca6678-56ba-5032-b04c-b1ed965dffb4
|
|
@@ -51,7 +51,6 @@ 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.
|
|
55
54
|
|
|
56
55
|
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.
|
|
57
56
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.8.0
|
|
3
|
+
"version": "0.8.0",
|
|
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
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.8.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|