@sybelblue/prairielearn-navigator 1.1.0 → 1.1.2

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.
@@ -1,9 +1,22 @@
1
+ import * as z from "zod";
1
2
  import { Diagnostic } from "./diagnostic";
2
3
  import { PlVersion } from "./plVersion";
3
4
  import { RuleId } from "./rules/registry";
4
5
  /** Looked up in the course root, next to infoCourse.json. */
5
6
  export declare const configFileName = ".pl-navigator.jsonc";
6
- type RuleSetting = "off" | "warning" | "error";
7
+ export declare const ruleSetting: z.ZodEnum<{
8
+ error: "error";
9
+ warning: "warning";
10
+ info: "info";
11
+ off: "off";
12
+ }>;
13
+ type RuleSetting = z.infer<typeof ruleSetting>;
14
+ export declare const severitySetting: z.ZodEnum<{
15
+ error: "error";
16
+ warning: "warning";
17
+ info: "info";
18
+ ignored: "ignored";
19
+ }>;
7
20
  /** A glob and the directory it is relative to. */
8
21
  interface Exclude {
9
22
  base: string;
@@ -20,7 +33,7 @@ export interface NavigatorConfig {
20
33
  /** Whether `file` matches one of the config's `excludes`. */
21
34
  export declare function isExcluded(config: NavigatorConfig, file: string): boolean;
22
35
  /** Same keys as the config file; from CLI flags or VS Code settings. */
23
- export type ConfigOverrides = Partial<Record<keyof NavigatorConfig, unknown>>;
36
+ export type ConfigOverrides = Partial<Record<keyof typeof fields, unknown>>;
24
37
  /** A config problem, located in `file` when it came from one. */
25
38
  export type ConfigProblem = Diagnostic & {
26
39
  file?: string;
@@ -43,4 +56,25 @@ export declare function loadConfig(courseRoot: string | null, options?: {
43
56
  overridesBaseDir?: string;
44
57
  defaults?: Partial<NavigatorConfig>;
45
58
  }): LoadedConfig;
59
+ type Report = (message: string, path: (string | number)[], severity?: Diagnostic["severity"]) => void;
60
+ /** One config source being applied. */
61
+ interface FieldContext {
62
+ config: NavigatorConfig;
63
+ /** What relative paths in this source are relative to. */
64
+ baseDir: string;
65
+ report: Report;
66
+ }
67
+ type Field = (key: string, value: unknown, ctx: FieldContext) => void;
68
+ declare const fields: {
69
+ unusedQuestions: Field;
70
+ unusedTags: Field;
71
+ unusedAssessmentSets: Field;
72
+ missingQuestionInfo: Field;
73
+ plVersion: Field;
74
+ schemaCacheDir: Field;
75
+ excludes: Field;
76
+ rules: Field;
77
+ };
78
+ /** Every key a config source may set, besides `$schema`. */
79
+ export declare const configKeys: readonly string[];
46
80
  export {};
@@ -3,11 +3,22 @@
3
3
  * Ranges are character offsets into the checked text; adapters convert them
4
4
  * to their own position types.
5
5
  */
6
- type Severity = "error" | "warning";
6
+ type Severity = "error" | "warning" | "info";
7
7
  interface RelatedInfo {
8
8
  path: string;
9
9
  message: string;
10
10
  }
11
+ export type DiagnosticMetadata = {
12
+ kind: "schema-required-property";
13
+ /** JSON path of the object that requires the property. */
14
+ path: (string | number)[];
15
+ property: string;
16
+ } | {
17
+ kind: "schema-pattern";
18
+ /** JSON path of the value that failed the pattern. */
19
+ path: (string | number)[];
20
+ pattern: string;
21
+ };
11
22
  export interface Diagnostic {
12
23
  startOffset: number;
13
24
  endOffset: number;
@@ -15,6 +26,7 @@ export interface Diagnostic {
15
26
  severity: Severity;
16
27
  code?: string;
17
28
  related?: RelatedInfo[];
29
+ metadata?: DiagnosticMetadata;
18
30
  }
19
31
  /** 1-based line/column for a character offset. */
20
32
  export declare function offsetToPosition(text: string, offset: number): {
@@ -0,0 +1,13 @@
1
+ /** A directory under questions/ that PrairieLearn will fail to load. */
2
+ export interface QuestionDirWithoutInfo {
3
+ /** Absolute. */
4
+ dir: string;
5
+ /** The file the diagnostic is shown on: question.html, else the first file. */
6
+ anchor: string;
7
+ }
8
+ /**
9
+ * Directories under `<courseRoot>/questions` with files but no info.json and
10
+ * no question anywhere below them. PrairieLearn walks questions/ the same way
11
+ * (skipping hidden entries) and records each as "Missing JSON file".
12
+ */
13
+ export declare function questionDirsWithoutInfo(courseRoot: string): QuestionDirWithoutInfo[];
@@ -15,14 +15,23 @@ export interface Use {
15
15
  column: number;
16
16
  };
17
17
  }
18
+ /** Names defined course-wide in infoCourse.json that a file uses. */
19
+ export interface UsedNames {
20
+ /** A question's `tags`. */
21
+ tags: string[];
22
+ /** An assessment's `set`. */
23
+ assessmentSets: string[];
24
+ }
18
25
  /**
19
26
  * Reverse index of every file reference in one course: which files
20
- * reference a given target. Updated one file at a time.
27
+ * reference a given target. Also records the tags and assessment sets each
28
+ * file uses. Updated one file at a time.
21
29
  */
22
30
  export declare class ReferenceIndex {
23
31
  readonly courseRoot: string;
24
32
  private plDate;
25
33
  private usesByFile;
34
+ private namesByFile;
26
35
  private filesByTarget;
27
36
  /** How many targets lie under each directory, to answer directory events quickly. */
28
37
  private targetsUnder;
@@ -34,6 +43,10 @@ export declare class ReferenceIndex {
34
43
  remove(file: string): void;
35
44
  /** Every file holding references. */
36
45
  files(): string[];
46
+ /** Tags and assessment sets `file` uses, if it is a question or assessment. */
47
+ namesIn(file: string): UsedNames | undefined;
48
+ /** Every name of this kind used anywhere in the course. */
49
+ namesUsed(kind: keyof UsedNames): Set<string>;
37
50
  /** References made by `file`. */
38
51
  usesIn(file: string): Use[];
39
52
  /**
@@ -12,3 +12,7 @@ export declare function schemaRule(name: SchemaName): RuleImpl;
12
12
  export declare function referencesExist(rule: string): RuleImpl;
13
13
  /** Warns on every occurrence of a question used more than once in a file. */
14
14
  export declare const duplicateQuestionIds: RuleImpl;
15
+ /** Notes a question that no assessment uses, on its title. */
16
+ export declare const unusedQuestion: RuleImpl;
17
+ export declare const unusedTags: RuleImpl;
18
+ export declare const unusedAssessmentSets: RuleImpl;
@@ -14,6 +14,10 @@ declare const otherRules: {
14
14
  readonly "json-syntax": (readonly ["*", "infoCourse.json" | "courseInstances/**/infoCourseInstance.json" | "courseInstances/**/assessments/**/infoAssessment.json" | "questions/**/info.json" | "elements/*/info.json" | "elementExtensions/*/*/info.json", import("./types").RuleImpl])[];
15
15
  readonly schema: readonly [readonly ["*", "infoCourse.json", import("./types").RuleImpl], readonly ["*", "courseInstances/**/infoCourseInstance.json", import("./types").RuleImpl], readonly ["*", "courseInstances/**/assessments/**/infoAssessment.json", import("./types").RuleImpl], readonly ["*", "questions/**/info.json", import("./types").RuleImpl], readonly ["*", "elements/*/info.json", import("./types").RuleImpl], readonly ["*", "elementExtensions/*/*/info.json", import("./types").RuleImpl]];
16
16
  readonly "duplicate-question-id": readonly [readonly ["*", "courseInstances/**/assessments/**/infoAssessment.json", import("./types").RuleImpl]];
17
+ readonly "unused-question": readonly [readonly ["*", "questions/**/info.json", import("./types").RuleImpl]];
18
+ readonly "unused-tag": readonly [readonly ["*", "infoCourse.json", import("./types").RuleImpl]];
19
+ readonly "unused-assessment-set": readonly [readonly ["*", "infoCourse.json", import("./types").RuleImpl]];
20
+ readonly "question-info-missing": readonly [];
17
21
  };
18
22
  /**
19
23
  * Every validation rule, by id. Ids appear in diagnostic codes as
@@ -21,6 +25,8 @@ declare const otherRules: {
21
25
  */
22
26
  export declare const rules: Record<RuleId, readonly RuleEntry[]>;
23
27
  export type RuleId = keyof typeof otherRules | RefRuleId;
28
+ /** Rules that read the reference index; without one they report nothing. */
29
+ export declare const indexedRules: readonly RuleId[];
24
30
  export declare function isRuleId(id: string): id is RuleId;
25
31
  export declare function ruleDiagnosticCode(id: RuleId): string;
26
32
  export {};
@@ -1,6 +1,7 @@
1
1
  import { NavigatorConfig } from "../config";
2
2
  import { Diagnostic } from "../diagnostic";
3
3
  import { IsoDate } from "../plVersion";
4
+ import { ReferenceIndex } from "../referenceIndex";
4
5
  import { SchemaStore } from "../schemas";
5
6
  import { RuleId } from "./registry";
6
7
  import { RuleImpl, RuleTable } from "./types";
@@ -15,4 +16,10 @@ export declare function runRules(filePath: string, text: string, options: {
15
16
  courseRoot: string;
16
17
  config: NavigatorConfig;
17
18
  schemas: SchemaStore;
19
+ references?: ReferenceIndex;
18
20
  }): Promise<Diagnostic[]>;
21
+ /**
22
+ * Runs the rules that look at a course as a whole rather than one file.
23
+ * Returns diagnostics by the absolute path of the file each is shown on.
24
+ */
25
+ export declare function runCourseRules(courseRoot: string, config: NavigatorConfig): Map<string, Diagnostic[]>;
@@ -1,6 +1,7 @@
1
1
  import { Diagnostic } from "../diagnostic";
2
2
  import { JsonDoc } from "../json";
3
3
  import { IsoDate, PlVersion, VersionRange } from "../plVersion";
4
+ import { ReferenceIndex } from "../referenceIndex";
4
5
  import { SchemaStore } from "../schemas";
5
6
  /** Everything a rule may inspect about the file being checked. */
6
7
  export interface RuleContext {
@@ -14,6 +15,11 @@ export interface RuleContext {
14
15
  /** The date `plVersion` stands for, used to select version-ranged rules. */
15
16
  plDate: IsoDate;
16
17
  schemas: SchemaStore;
18
+ /**
19
+ * The course's reference index, when the caller has built one; cross-file
20
+ * rules report nothing without it.
21
+ */
22
+ references?: ReferenceIndex;
17
23
  }
18
24
  export type RuleImpl = (ctx: RuleContext) => Diagnostic[] | Promise<Diagnostic[]>;
19
25
  /**