@doccov/sdk 0.5.7 → 0.5.9
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/index.d.ts +79 -1
- package/dist/index.js +508 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -764,4 +764,82 @@ declare function readPackageJson(fs: FileSystem, dir: string): Promise<PackageJs
|
|
|
764
764
|
* ```
|
|
765
765
|
*/
|
|
766
766
|
declare function analyzeProject2(fs: FileSystem, options?: AnalyzeProjectOptions): Promise<ProjectInfo>;
|
|
767
|
-
|
|
767
|
+
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
768
|
+
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
769
|
+
type LintSeverity = "error" | "warn" | "off";
|
|
770
|
+
interface LintViolation {
|
|
771
|
+
rule: string;
|
|
772
|
+
severity: "error" | "warn";
|
|
773
|
+
message: string;
|
|
774
|
+
line?: number;
|
|
775
|
+
fixable: boolean;
|
|
776
|
+
}
|
|
777
|
+
interface LintRule {
|
|
778
|
+
name: string;
|
|
779
|
+
defaultSeverity: LintSeverity;
|
|
780
|
+
check(exp: SpecExport3, rawJSDoc?: string): LintViolation[];
|
|
781
|
+
fix?(exp: SpecExport3, rawJSDoc?: string): JSDocPatch | null;
|
|
782
|
+
}
|
|
783
|
+
interface LintConfig {
|
|
784
|
+
rules: Record<string, LintSeverity>;
|
|
785
|
+
}
|
|
786
|
+
interface LintResult {
|
|
787
|
+
violations: LintViolation[];
|
|
788
|
+
errorCount: number;
|
|
789
|
+
warningCount: number;
|
|
790
|
+
fixableCount: number;
|
|
791
|
+
}
|
|
792
|
+
/** All available lint rules */
|
|
793
|
+
declare const allRules: LintRule[];
|
|
794
|
+
/** Default configuration with rule defaults */
|
|
795
|
+
declare function getDefaultConfig(): LintConfig;
|
|
796
|
+
/** Get a rule by name */
|
|
797
|
+
declare function getRule(name: string): LintRule | undefined;
|
|
798
|
+
/** Lint a single */
|
|
799
|
+
declare function lintExport(exp: SpecExport4, rawJSDoc?: string, config?: LintConfig): LintViolation[];
|
|
800
|
+
/** Lint multiple exports and aggregate results */
|
|
801
|
+
declare function lintExports(exports: Array<{
|
|
802
|
+
export: SpecExport4;
|
|
803
|
+
rawJSDoc?: string;
|
|
804
|
+
}>, config?: LintConfig): LintResult;
|
|
805
|
+
/** Merge user config with defaults */
|
|
806
|
+
declare function mergeConfig(userConfig: Partial<LintConfig>): LintConfig;
|
|
807
|
+
declare const consistentParamStyle: LintRule;
|
|
808
|
+
declare const noEmptyReturns: LintRule;
|
|
809
|
+
declare const requireDescription: LintRule;
|
|
810
|
+
declare const requireExample: LintRule;
|
|
811
|
+
interface ExampleTypeError {
|
|
812
|
+
/** Index of the example in the examples array */
|
|
813
|
+
exampleIndex: number;
|
|
814
|
+
/** Line number within the example (1-based) */
|
|
815
|
+
line: number;
|
|
816
|
+
/** Column number (1-based) */
|
|
817
|
+
column: number;
|
|
818
|
+
/** Error message from TypeScript */
|
|
819
|
+
message: string;
|
|
820
|
+
/** TypeScript diagnostic code */
|
|
821
|
+
code: number;
|
|
822
|
+
}
|
|
823
|
+
interface TypecheckResult {
|
|
824
|
+
/** All type errors found across examples */
|
|
825
|
+
errors: ExampleTypeError[];
|
|
826
|
+
/** Number of examples that passed */
|
|
827
|
+
passed: number;
|
|
828
|
+
/** Number of examples that failed */
|
|
829
|
+
failed: number;
|
|
830
|
+
}
|
|
831
|
+
interface TypecheckOptions {
|
|
832
|
+
/** Path to tsconfig.json (auto-detected if not provided) */
|
|
833
|
+
tsconfig?: string;
|
|
834
|
+
/** Package name for imports (auto-detected from package.json if not provided) */
|
|
835
|
+
packageName?: string;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Type-check a single example
|
|
839
|
+
*/
|
|
840
|
+
declare function typecheckExample(example: string, packagePath: string, options?: TypecheckOptions): ExampleTypeError[];
|
|
841
|
+
/**
|
|
842
|
+
* Type-check multiple examples
|
|
843
|
+
*/
|
|
844
|
+
declare function typecheckExamples(examples: string[], packagePath: string, options?: TypecheckOptions): TypecheckResult;
|
|
845
|
+
export { typecheckExamples, typecheckExample, serializeJSDoc, safeParseJson, runExamplesWithPackage, runExamples, runExample, requireExample, requireDescription, readPackageJson, parseMarkdownFiles, parseMarkdownFile, parseJSDocToPatch, parseAssertions, noEmptyReturns, mergeFixes, mergeConfig, lintExports, lintExport, isFixableDrift, isExecutableLang, hasNonAssertionComments, hasDocsImpact, hasDocsForExport, getUndocumentedExports, getRunCommand, getRule, getPrimaryBuildScript, getInstallCommand, getDocumentedExports, getDocsImpactSummary, getDefaultConfig, generateFixesForExport, generateFix, formatPackageList, findRemovedReferences, findPackageByName, findJSDocLocation, findExportReferences, findDeprecatedReferences, extractPackageSpec, extractImports, extractFunctionCalls, diffSpecWithDocs, detectPackageManager, detectMonorepo, detectExampleRuntimeErrors, detectExampleAssertionFailures, detectEntryPoint, detectBuildInfo, createSourceFile, consistentParamStyle, categorizeDrifts, blockReferencesExport, applyPatchToJSDoc, applyEdits, analyzeProject2 as analyzeProject, analyzeFile, analyzeDocsImpact, analyze, allRules, WorkspacePackage, TypecheckResult, TypecheckOptions, SpecDiffWithDocs, SandboxFileSystem, RunExamplesWithPackageResult, RunExamplesWithPackageOptions, RunExampleOptions, ProjectInfo, PackageManagerInfo, PackageManager, PackageJson, PackageExports, OpenPkgSpec, OpenPkgOptions, OpenPkg4 as OpenPkg, NodeFileSystem, MonorepoType, MonorepoInfo, MemberChange, MarkdownDocFile, MarkdownCodeBlock, LintViolation, LintSeverity, LintRule, LintResult, LintConfig, JSDocTag, JSDocReturn, JSDocPatch, JSDocParam, JSDocEdit, FixType, FixSuggestion, FilterOptions, FileSystem, ExportReference, ExampleTypeError, ExampleRunResult, EntryPointSource, EntryPointInfo, DocsImpactResult, DocsImpactReference, DocsImpact, DocsChangeType, DocCovOptions, DocCov, DiffWithDocsOptions, Diagnostic, BuildInfo, ApplyEditsResult, AnalyzeProjectOptions, AnalyzeOptions, AnalysisResult };
|
package/dist/index.js
CHANGED
|
@@ -1989,6 +1989,199 @@ var BUILTIN_TYPE_SCHEMAS = {
|
|
|
1989
1989
|
BigInt64Array: { type: "string", format: "byte" },
|
|
1990
1990
|
BigUint64Array: { type: "string", format: "byte" }
|
|
1991
1991
|
};
|
|
1992
|
+
var TYPEBOX_PRIMITIVE_MAP = {
|
|
1993
|
+
TString: { type: "string" },
|
|
1994
|
+
TNumber: { type: "number" },
|
|
1995
|
+
TBoolean: { type: "boolean" },
|
|
1996
|
+
TInteger: { type: "integer" },
|
|
1997
|
+
TNull: { type: "null" },
|
|
1998
|
+
TAny: {},
|
|
1999
|
+
TUnknown: {},
|
|
2000
|
+
TNever: { not: {} },
|
|
2001
|
+
TVoid: { type: "null" },
|
|
2002
|
+
TUndefined: { type: "null" }
|
|
2003
|
+
};
|
|
2004
|
+
function isTypeBoxSchemaType(symbolName) {
|
|
2005
|
+
return /^T[A-Z][a-zA-Z]*$/.test(symbolName);
|
|
2006
|
+
}
|
|
2007
|
+
function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited) {
|
|
2008
|
+
const symbol = type.getSymbol();
|
|
2009
|
+
if (!symbol)
|
|
2010
|
+
return null;
|
|
2011
|
+
const symbolName = symbol.getName();
|
|
2012
|
+
if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
|
|
2013
|
+
return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
|
|
2014
|
+
}
|
|
2015
|
+
const objectType = type;
|
|
2016
|
+
if (!(objectType.objectFlags & ts2.ObjectFlags.Reference)) {
|
|
2017
|
+
return null;
|
|
2018
|
+
}
|
|
2019
|
+
const typeRef = type;
|
|
2020
|
+
const typeArgs = typeRef.typeArguments;
|
|
2021
|
+
switch (symbolName) {
|
|
2022
|
+
case "TObject": {
|
|
2023
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2024
|
+
return { type: "object" };
|
|
2025
|
+
}
|
|
2026
|
+
const propsType = typeArgs[0];
|
|
2027
|
+
const properties = {};
|
|
2028
|
+
const required = [];
|
|
2029
|
+
for (const prop of propsType.getProperties()) {
|
|
2030
|
+
const propName = prop.getName();
|
|
2031
|
+
const propType = getPropertyType(prop, propsType, typeChecker);
|
|
2032
|
+
const propSymbol = propType.getSymbol();
|
|
2033
|
+
const propSymbolName = propSymbol?.getName();
|
|
2034
|
+
if (propSymbolName && typeRefs.has(propSymbolName)) {
|
|
2035
|
+
properties[propName] = { $ref: `#/types/${propSymbolName}` };
|
|
2036
|
+
} else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
|
|
2037
|
+
const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited);
|
|
2038
|
+
properties[propName] = nested ?? { type: "object" };
|
|
2039
|
+
} else {
|
|
2040
|
+
properties[propName] = formatTypeReference(propType, typeChecker, typeRefs, referencedTypes, visited);
|
|
2041
|
+
}
|
|
2042
|
+
if (propSymbolName !== "TOptional") {
|
|
2043
|
+
required.push(propName);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
const schema = { type: "object", properties };
|
|
2047
|
+
if (required.length > 0) {
|
|
2048
|
+
schema.required = required;
|
|
2049
|
+
}
|
|
2050
|
+
return schema;
|
|
2051
|
+
}
|
|
2052
|
+
case "TArray": {
|
|
2053
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2054
|
+
return { type: "array" };
|
|
2055
|
+
}
|
|
2056
|
+
const itemType = typeArgs[0];
|
|
2057
|
+
const itemSymbol = itemType.getSymbol();
|
|
2058
|
+
const itemSymbolName = itemSymbol?.getName();
|
|
2059
|
+
let items;
|
|
2060
|
+
if (itemSymbolName && typeRefs.has(itemSymbolName)) {
|
|
2061
|
+
items = { $ref: `#/types/${itemSymbolName}` };
|
|
2062
|
+
} else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
|
|
2063
|
+
items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited) ?? { type: "object" };
|
|
2064
|
+
} else {
|
|
2065
|
+
items = formatTypeReference(itemType, typeChecker, typeRefs, referencedTypes, visited);
|
|
2066
|
+
}
|
|
2067
|
+
return { type: "array", items };
|
|
2068
|
+
}
|
|
2069
|
+
case "TUnion": {
|
|
2070
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2071
|
+
return { anyOf: [] };
|
|
2072
|
+
}
|
|
2073
|
+
const tupleType = typeArgs[0];
|
|
2074
|
+
const members = [];
|
|
2075
|
+
if (tupleType.isUnion()) {
|
|
2076
|
+
for (const memberType of tupleType.types) {
|
|
2077
|
+
const memberSymbol = memberType.getSymbol();
|
|
2078
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
2079
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
2080
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
2081
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
2082
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited) ?? { type: "object" });
|
|
2083
|
+
} else {
|
|
2084
|
+
members.push(formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes, visited));
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
} else if (tupleType.typeArguments) {
|
|
2088
|
+
for (const memberType of tupleType.typeArguments) {
|
|
2089
|
+
const memberSymbol = memberType.getSymbol();
|
|
2090
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
2091
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
2092
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
2093
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
2094
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited) ?? { type: "object" });
|
|
2095
|
+
} else {
|
|
2096
|
+
members.push(formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes, visited));
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return { anyOf: members };
|
|
2101
|
+
}
|
|
2102
|
+
case "TIntersect": {
|
|
2103
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2104
|
+
return { allOf: [] };
|
|
2105
|
+
}
|
|
2106
|
+
const tupleType = typeArgs[0];
|
|
2107
|
+
const members = [];
|
|
2108
|
+
if (tupleType.typeArguments) {
|
|
2109
|
+
for (const memberType of tupleType.typeArguments) {
|
|
2110
|
+
const memberSymbol = memberType.getSymbol();
|
|
2111
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
2112
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
2113
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
2114
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
2115
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited) ?? { type: "object" });
|
|
2116
|
+
} else {
|
|
2117
|
+
members.push(formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes, visited));
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
return { allOf: members };
|
|
2122
|
+
}
|
|
2123
|
+
case "TOptional": {
|
|
2124
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2125
|
+
return {};
|
|
2126
|
+
}
|
|
2127
|
+
const innerType = typeArgs[0];
|
|
2128
|
+
const innerSymbol = innerType.getSymbol();
|
|
2129
|
+
const innerSymbolName = innerSymbol?.getName();
|
|
2130
|
+
if (innerSymbolName && typeRefs.has(innerSymbolName)) {
|
|
2131
|
+
return { $ref: `#/types/${innerSymbolName}` };
|
|
2132
|
+
} else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
|
|
2133
|
+
return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited) ?? { type: "object" };
|
|
2134
|
+
}
|
|
2135
|
+
return formatTypeReference(innerType, typeChecker, typeRefs, referencedTypes, visited);
|
|
2136
|
+
}
|
|
2137
|
+
case "TLiteral": {
|
|
2138
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2139
|
+
return { enum: [] };
|
|
2140
|
+
}
|
|
2141
|
+
const literalType = typeArgs[0];
|
|
2142
|
+
if (literalType.isLiteral()) {
|
|
2143
|
+
const value = literalType.value;
|
|
2144
|
+
return { enum: [value] };
|
|
2145
|
+
}
|
|
2146
|
+
const literalStr = typeChecker.typeToString(literalType);
|
|
2147
|
+
if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
|
|
2148
|
+
return { enum: [literalStr.slice(1, -1)] };
|
|
2149
|
+
}
|
|
2150
|
+
return { enum: [literalStr] };
|
|
2151
|
+
}
|
|
2152
|
+
case "TRecord": {
|
|
2153
|
+
if (!typeArgs || typeArgs.length < 2) {
|
|
2154
|
+
return { type: "object", additionalProperties: true };
|
|
2155
|
+
}
|
|
2156
|
+
const valueType = typeArgs[1];
|
|
2157
|
+
const valueSymbol = valueType.getSymbol();
|
|
2158
|
+
const valueSymbolName = valueSymbol?.getName();
|
|
2159
|
+
let additionalProperties;
|
|
2160
|
+
if (valueSymbolName && typeRefs.has(valueSymbolName)) {
|
|
2161
|
+
additionalProperties = { $ref: `#/types/${valueSymbolName}` };
|
|
2162
|
+
} else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
|
|
2163
|
+
additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited) ?? true;
|
|
2164
|
+
} else {
|
|
2165
|
+
additionalProperties = formatTypeReference(valueType, typeChecker, typeRefs, referencedTypes, visited);
|
|
2166
|
+
}
|
|
2167
|
+
return { type: "object", additionalProperties };
|
|
2168
|
+
}
|
|
2169
|
+
case "TRef": {
|
|
2170
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
2171
|
+
return { $ref: "#/types/unknown" };
|
|
2172
|
+
}
|
|
2173
|
+
const refType = typeArgs[0];
|
|
2174
|
+
const refSymbol = refType.getSymbol();
|
|
2175
|
+
const refSymbolName = refSymbol?.getName();
|
|
2176
|
+
if (refSymbolName) {
|
|
2177
|
+
return { $ref: `#/types/${refSymbolName}` };
|
|
2178
|
+
}
|
|
2179
|
+
return { type: "object" };
|
|
2180
|
+
}
|
|
2181
|
+
default:
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
1992
2185
|
function isObjectLiteralType(type) {
|
|
1993
2186
|
if (!(type.getFlags() & ts2.TypeFlags.Object)) {
|
|
1994
2187
|
return false;
|
|
@@ -2404,6 +2597,12 @@ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visit
|
|
|
2404
2597
|
if (builtInSchema) {
|
|
2405
2598
|
return { ...builtInSchema };
|
|
2406
2599
|
}
|
|
2600
|
+
if (isTypeBoxSchemaType(symbolName)) {
|
|
2601
|
+
const typeBoxSchema = formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited);
|
|
2602
|
+
if (typeBoxSchema) {
|
|
2603
|
+
return typeBoxSchema;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2407
2606
|
if (referencedTypes && !isBuiltInType(symbolName)) {
|
|
2408
2607
|
referencedTypes.add(symbolName);
|
|
2409
2608
|
return { $ref: `#/types/${symbolName}` };
|
|
@@ -5938,18 +6137,323 @@ async function analyzeProject(fs7, options = {}) {
|
|
|
5938
6137
|
]);
|
|
5939
6138
|
return { packageManager, monorepo, entryPoint, build };
|
|
5940
6139
|
}
|
|
6140
|
+
// src/lint/rules/consistent-param-style.ts
|
|
6141
|
+
var consistentParamStyle = {
|
|
6142
|
+
name: "consistent-param-style",
|
|
6143
|
+
defaultSeverity: "off",
|
|
6144
|
+
check(exp, rawJSDoc) {
|
|
6145
|
+
if (!rawJSDoc)
|
|
6146
|
+
return [];
|
|
6147
|
+
const violations = [];
|
|
6148
|
+
const paramRegex = /@param\s+(?:\{[^}]+\}\s+)?(\S+)\s+([^@\n]+)/g;
|
|
6149
|
+
let match;
|
|
6150
|
+
while ((match = paramRegex.exec(rawJSDoc)) !== null) {
|
|
6151
|
+
const paramName = match[1];
|
|
6152
|
+
const rest = match[2].trim();
|
|
6153
|
+
if (rest && !rest.startsWith("-") && !rest.startsWith("–")) {
|
|
6154
|
+
violations.push({
|
|
6155
|
+
rule: "consistent-param-style",
|
|
6156
|
+
severity: "warn",
|
|
6157
|
+
message: `Parameter '${paramName}' should use dash separator: @param ${paramName} - description`,
|
|
6158
|
+
fixable: true
|
|
6159
|
+
});
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
return violations;
|
|
6163
|
+
},
|
|
6164
|
+
fix(_exp, rawJSDoc) {
|
|
6165
|
+
if (!rawJSDoc)
|
|
6166
|
+
return null;
|
|
6167
|
+
const patch = parseJSDocToPatch(rawJSDoc);
|
|
6168
|
+
if (!patch.params || patch.params.length === 0)
|
|
6169
|
+
return null;
|
|
6170
|
+
return patch;
|
|
6171
|
+
}
|
|
6172
|
+
};
|
|
6173
|
+
// src/lint/rules/no-empty-returns.ts
|
|
6174
|
+
var noEmptyReturns = {
|
|
6175
|
+
name: "no-empty-returns",
|
|
6176
|
+
defaultSeverity: "warn",
|
|
6177
|
+
check(exp, rawJSDoc) {
|
|
6178
|
+
if (!rawJSDoc)
|
|
6179
|
+
return [];
|
|
6180
|
+
const returnsMatch = rawJSDoc.match(/@returns?\s*(?:\{[^}]*\})?\s*$/m);
|
|
6181
|
+
if (returnsMatch) {
|
|
6182
|
+
return [
|
|
6183
|
+
{
|
|
6184
|
+
rule: "no-empty-returns",
|
|
6185
|
+
severity: "warn",
|
|
6186
|
+
message: `Export '${exp.name}' has @returns without a description`,
|
|
6187
|
+
fixable: false
|
|
6188
|
+
}
|
|
6189
|
+
];
|
|
6190
|
+
}
|
|
6191
|
+
const returnsTypeOnly = rawJSDoc.match(/@returns?\s+\{[^}]+\}\s*$/m);
|
|
6192
|
+
if (returnsTypeOnly) {
|
|
6193
|
+
return [
|
|
6194
|
+
{
|
|
6195
|
+
rule: "no-empty-returns",
|
|
6196
|
+
severity: "warn",
|
|
6197
|
+
message: `Export '${exp.name}' has @returns without a description`,
|
|
6198
|
+
fixable: false
|
|
6199
|
+
}
|
|
6200
|
+
];
|
|
6201
|
+
}
|
|
6202
|
+
return [];
|
|
6203
|
+
}
|
|
6204
|
+
};
|
|
6205
|
+
// src/lint/rules/require-description.ts
|
|
6206
|
+
var requireDescription = {
|
|
6207
|
+
name: "require-description",
|
|
6208
|
+
defaultSeverity: "warn",
|
|
6209
|
+
check(exp) {
|
|
6210
|
+
if (!exp.description || exp.description.trim() === "") {
|
|
6211
|
+
return [
|
|
6212
|
+
{
|
|
6213
|
+
rule: "require-description",
|
|
6214
|
+
severity: "warn",
|
|
6215
|
+
message: `Export '${exp.name}' is missing a description`,
|
|
6216
|
+
fixable: false
|
|
6217
|
+
}
|
|
6218
|
+
];
|
|
6219
|
+
}
|
|
6220
|
+
return [];
|
|
6221
|
+
}
|
|
6222
|
+
};
|
|
6223
|
+
// src/lint/rules/require-example.ts
|
|
6224
|
+
var requireExample = {
|
|
6225
|
+
name: "require-example",
|
|
6226
|
+
defaultSeverity: "off",
|
|
6227
|
+
check(exp) {
|
|
6228
|
+
if (exp.kind !== "function" && exp.kind !== "method") {
|
|
6229
|
+
return [];
|
|
6230
|
+
}
|
|
6231
|
+
if (!exp.examples || exp.examples.length === 0) {
|
|
6232
|
+
return [
|
|
6233
|
+
{
|
|
6234
|
+
rule: "require-example",
|
|
6235
|
+
severity: "warn",
|
|
6236
|
+
message: `Function '${exp.name}' is missing an @example`,
|
|
6237
|
+
fixable: false
|
|
6238
|
+
}
|
|
6239
|
+
];
|
|
6240
|
+
}
|
|
6241
|
+
return [];
|
|
6242
|
+
}
|
|
6243
|
+
};
|
|
6244
|
+
// src/lint/engine.ts
|
|
6245
|
+
var allRules = [
|
|
6246
|
+
requireDescription,
|
|
6247
|
+
requireExample,
|
|
6248
|
+
noEmptyReturns,
|
|
6249
|
+
consistentParamStyle
|
|
6250
|
+
];
|
|
6251
|
+
function getDefaultConfig() {
|
|
6252
|
+
const rules = {};
|
|
6253
|
+
for (const rule of allRules) {
|
|
6254
|
+
rules[rule.name] = rule.defaultSeverity;
|
|
6255
|
+
}
|
|
6256
|
+
return { rules };
|
|
6257
|
+
}
|
|
6258
|
+
function getRule(name) {
|
|
6259
|
+
return allRules.find((r) => r.name === name);
|
|
6260
|
+
}
|
|
6261
|
+
function lintExport(exp, rawJSDoc, config = getDefaultConfig()) {
|
|
6262
|
+
const violations = [];
|
|
6263
|
+
for (const rule of allRules) {
|
|
6264
|
+
const severity = config.rules[rule.name];
|
|
6265
|
+
if (severity === "off")
|
|
6266
|
+
continue;
|
|
6267
|
+
const ruleViolations = rule.check(exp, rawJSDoc);
|
|
6268
|
+
for (const v of ruleViolations) {
|
|
6269
|
+
violations.push({
|
|
6270
|
+
...v,
|
|
6271
|
+
severity: severity === "error" ? "error" : "warn"
|
|
6272
|
+
});
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
return violations;
|
|
6276
|
+
}
|
|
6277
|
+
function lintExports(exports, config = getDefaultConfig()) {
|
|
6278
|
+
const violations = [];
|
|
6279
|
+
for (const { export: exp, rawJSDoc } of exports) {
|
|
6280
|
+
violations.push(...lintExport(exp, rawJSDoc, config));
|
|
6281
|
+
}
|
|
6282
|
+
return {
|
|
6283
|
+
violations,
|
|
6284
|
+
errorCount: violations.filter((v) => v.severity === "error").length,
|
|
6285
|
+
warningCount: violations.filter((v) => v.severity === "warn").length,
|
|
6286
|
+
fixableCount: violations.filter((v) => v.fixable).length
|
|
6287
|
+
};
|
|
6288
|
+
}
|
|
6289
|
+
function mergeConfig(userConfig) {
|
|
6290
|
+
const defaults = getDefaultConfig();
|
|
6291
|
+
return {
|
|
6292
|
+
rules: {
|
|
6293
|
+
...defaults.rules,
|
|
6294
|
+
...userConfig.rules
|
|
6295
|
+
}
|
|
6296
|
+
};
|
|
6297
|
+
}
|
|
6298
|
+
// src/typecheck/example-typechecker.ts
|
|
6299
|
+
import * as fs7 from "node:fs";
|
|
6300
|
+
import * as path8 from "node:path";
|
|
6301
|
+
import ts3 from "typescript";
|
|
6302
|
+
function stripCodeBlockMarkers2(code) {
|
|
6303
|
+
return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
|
|
6304
|
+
}
|
|
6305
|
+
function getPackageName(packagePath) {
|
|
6306
|
+
const pkgJsonPath = path8.join(packagePath, "package.json");
|
|
6307
|
+
if (fs7.existsSync(pkgJsonPath)) {
|
|
6308
|
+
try {
|
|
6309
|
+
const pkgJson = JSON.parse(fs7.readFileSync(pkgJsonPath, "utf-8"));
|
|
6310
|
+
return pkgJson.name;
|
|
6311
|
+
} catch {
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6315
|
+
return;
|
|
6316
|
+
}
|
|
6317
|
+
function findTsConfig(packagePath) {
|
|
6318
|
+
let dir = packagePath;
|
|
6319
|
+
while (dir !== path8.dirname(dir)) {
|
|
6320
|
+
const tsConfigPath = path8.join(dir, "tsconfig.json");
|
|
6321
|
+
if (fs7.existsSync(tsConfigPath)) {
|
|
6322
|
+
return tsConfigPath;
|
|
6323
|
+
}
|
|
6324
|
+
dir = path8.dirname(dir);
|
|
6325
|
+
}
|
|
6326
|
+
return;
|
|
6327
|
+
}
|
|
6328
|
+
function createVirtualSource(example, packageName, exportNames) {
|
|
6329
|
+
const cleanCode = stripCodeBlockMarkers2(example);
|
|
6330
|
+
const lines = [];
|
|
6331
|
+
if (packageName && exportNames && exportNames.length > 0) {
|
|
6332
|
+
lines.push(`import { ${exportNames.join(", ")} } from '${packageName}';`);
|
|
6333
|
+
lines.push("");
|
|
6334
|
+
}
|
|
6335
|
+
lines.push(cleanCode);
|
|
6336
|
+
return lines.join(`
|
|
6337
|
+
`);
|
|
6338
|
+
}
|
|
6339
|
+
function getCompilerOptions(tsconfigPath) {
|
|
6340
|
+
if (tsconfigPath && fs7.existsSync(tsconfigPath)) {
|
|
6341
|
+
const configFile = ts3.readConfigFile(tsconfigPath, ts3.sys.readFile);
|
|
6342
|
+
if (!configFile.error) {
|
|
6343
|
+
const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path8.dirname(tsconfigPath));
|
|
6344
|
+
return {
|
|
6345
|
+
...parsed.options,
|
|
6346
|
+
noEmit: true,
|
|
6347
|
+
skipLibCheck: true
|
|
6348
|
+
};
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
return {
|
|
6352
|
+
target: ts3.ScriptTarget.ES2022,
|
|
6353
|
+
module: ts3.ModuleKind.ESNext,
|
|
6354
|
+
moduleResolution: ts3.ModuleResolutionKind.Bundler,
|
|
6355
|
+
esModuleInterop: true,
|
|
6356
|
+
strict: true,
|
|
6357
|
+
noEmit: true,
|
|
6358
|
+
skipLibCheck: true
|
|
6359
|
+
};
|
|
6360
|
+
}
|
|
6361
|
+
function typecheckExample(example, packagePath, options = {}) {
|
|
6362
|
+
const errors = [];
|
|
6363
|
+
const cleanCode = stripCodeBlockMarkers2(example);
|
|
6364
|
+
const packageName = options.packageName ?? getPackageName(packagePath);
|
|
6365
|
+
const tsconfigPath = options.tsconfig ?? findTsConfig(packagePath);
|
|
6366
|
+
const compilerOptions = getCompilerOptions(tsconfigPath);
|
|
6367
|
+
const virtualSource = createVirtualSource(cleanCode, packageName);
|
|
6368
|
+
const virtualFileName = path8.join(packagePath, "__doccov_example__.ts");
|
|
6369
|
+
const hasImport = packageName !== undefined;
|
|
6370
|
+
const lineOffset = hasImport ? 2 : 0;
|
|
6371
|
+
const sourceFile = ts3.createSourceFile(virtualFileName, virtualSource, ts3.ScriptTarget.ES2022, true);
|
|
6372
|
+
const defaultHost = ts3.createCompilerHost(compilerOptions);
|
|
6373
|
+
const customHost = {
|
|
6374
|
+
...defaultHost,
|
|
6375
|
+
getSourceFile: (fileName, languageVersion) => {
|
|
6376
|
+
if (fileName === virtualFileName) {
|
|
6377
|
+
return sourceFile;
|
|
6378
|
+
}
|
|
6379
|
+
return defaultHost.getSourceFile(fileName, languageVersion);
|
|
6380
|
+
},
|
|
6381
|
+
fileExists: (fileName) => {
|
|
6382
|
+
if (fileName === virtualFileName)
|
|
6383
|
+
return true;
|
|
6384
|
+
return defaultHost.fileExists(fileName);
|
|
6385
|
+
},
|
|
6386
|
+
readFile: (fileName) => {
|
|
6387
|
+
if (fileName === virtualFileName)
|
|
6388
|
+
return virtualSource;
|
|
6389
|
+
return defaultHost.readFile(fileName);
|
|
6390
|
+
}
|
|
6391
|
+
};
|
|
6392
|
+
const program = ts3.createProgram([virtualFileName], compilerOptions, customHost);
|
|
6393
|
+
const diagnostics = ts3.getPreEmitDiagnostics(program, sourceFile);
|
|
6394
|
+
for (const diagnostic of diagnostics) {
|
|
6395
|
+
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
6396
|
+
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
6397
|
+
const exampleLine = line - lineOffset + 1;
|
|
6398
|
+
if (exampleLine < 1)
|
|
6399
|
+
continue;
|
|
6400
|
+
errors.push({
|
|
6401
|
+
exampleIndex: 0,
|
|
6402
|
+
line: exampleLine,
|
|
6403
|
+
column: character + 1,
|
|
6404
|
+
message: ts3.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
6405
|
+
`),
|
|
6406
|
+
code: diagnostic.code
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
return errors;
|
|
6411
|
+
}
|
|
6412
|
+
function typecheckExamples(examples, packagePath, options = {}) {
|
|
6413
|
+
const allErrors = [];
|
|
6414
|
+
let passed = 0;
|
|
6415
|
+
let failed = 0;
|
|
6416
|
+
for (let i = 0;i < examples.length; i++) {
|
|
6417
|
+
const example = examples[i];
|
|
6418
|
+
if (!example || typeof example !== "string" || !example.trim()) {
|
|
6419
|
+
continue;
|
|
6420
|
+
}
|
|
6421
|
+
const errors = typecheckExample(example, packagePath, options);
|
|
6422
|
+
for (const error of errors) {
|
|
6423
|
+
allErrors.push({ ...error, exampleIndex: i });
|
|
6424
|
+
}
|
|
6425
|
+
if (errors.length > 0) {
|
|
6426
|
+
failed++;
|
|
6427
|
+
} else {
|
|
6428
|
+
passed++;
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
6431
|
+
return {
|
|
6432
|
+
errors: allErrors,
|
|
6433
|
+
passed,
|
|
6434
|
+
failed
|
|
6435
|
+
};
|
|
6436
|
+
}
|
|
5941
6437
|
export {
|
|
6438
|
+
typecheckExamples,
|
|
6439
|
+
typecheckExample,
|
|
5942
6440
|
serializeJSDoc,
|
|
5943
6441
|
safeParseJson,
|
|
5944
6442
|
runExamplesWithPackage,
|
|
5945
6443
|
runExamples,
|
|
5946
6444
|
runExample,
|
|
6445
|
+
requireExample,
|
|
6446
|
+
requireDescription,
|
|
5947
6447
|
readPackageJson,
|
|
5948
6448
|
parseMarkdownFiles,
|
|
5949
6449
|
parseMarkdownFile,
|
|
5950
6450
|
parseJSDocToPatch,
|
|
5951
6451
|
parseAssertions,
|
|
6452
|
+
noEmptyReturns,
|
|
5952
6453
|
mergeFixes,
|
|
6454
|
+
mergeConfig,
|
|
6455
|
+
lintExports,
|
|
6456
|
+
lintExport,
|
|
5953
6457
|
isFixableDrift,
|
|
5954
6458
|
isExecutableLang,
|
|
5955
6459
|
hasNonAssertionComments,
|
|
@@ -5957,10 +6461,12 @@ export {
|
|
|
5957
6461
|
hasDocsForExport,
|
|
5958
6462
|
getUndocumentedExports,
|
|
5959
6463
|
getRunCommand,
|
|
6464
|
+
getRule,
|
|
5960
6465
|
getPrimaryBuildScript,
|
|
5961
6466
|
getInstallCommand2 as getInstallCommand,
|
|
5962
6467
|
getDocumentedExports,
|
|
5963
6468
|
getDocsImpactSummary,
|
|
6469
|
+
getDefaultConfig,
|
|
5964
6470
|
generateFixesForExport,
|
|
5965
6471
|
generateFix,
|
|
5966
6472
|
formatPackageList,
|
|
@@ -5980,6 +6486,7 @@ export {
|
|
|
5980
6486
|
detectEntryPoint,
|
|
5981
6487
|
detectBuildInfo,
|
|
5982
6488
|
createSourceFile,
|
|
6489
|
+
consistentParamStyle,
|
|
5983
6490
|
categorizeDrifts,
|
|
5984
6491
|
blockReferencesExport,
|
|
5985
6492
|
applyPatchToJSDoc,
|
|
@@ -5988,6 +6495,7 @@ export {
|
|
|
5988
6495
|
analyzeFile,
|
|
5989
6496
|
analyzeDocsImpact,
|
|
5990
6497
|
analyze,
|
|
6498
|
+
allRules,
|
|
5991
6499
|
SandboxFileSystem,
|
|
5992
6500
|
OpenPkg,
|
|
5993
6501
|
NodeFileSystem,
|