@sybelblue/prairielearn-navigator 1.1.2 → 1.2.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/README.md +12 -0
- package/dist/cli.js +8 -0
- package/dist/index.js +1 -1
- package/dist/shared.js +1 -0
- package/package.json +6 -3
- package/dist/cli.cjs +0 -8
- package/dist/core/config.d.ts +0 -80
- package/dist/core/course.d.ts +0 -7
- package/dist/core/courseFiles.d.ts +0 -14
- package/dist/core/diagnostic.d.ts +0 -36
- package/dist/core/html.d.ts +0 -20
- package/dist/core/json.d.ts +0 -32
- package/dist/core/plVersion.d.ts +0 -23
- package/dist/core/questionDirs.d.ts +0 -13
- package/dist/core/questionPaths.d.ts +0 -10
- package/dist/core/referenceIndex.d.ts +0 -71
- package/dist/core/references/bases.d.ts +0 -13
- package/dist/core/references/extract.d.ts +0 -34
- package/dist/core/references/specs.d.ts +0 -272
- package/dist/core/rules/impls.d.ts +0 -18
- package/dist/core/rules/registry.d.ts +0 -32
- package/dist/core/rules/run.d.ts +0 -25
- package/dist/core/rules/types.d.ts +0 -35
- package/dist/core/schemas.d.ts +0 -51
- package/dist/library.d.ts +0 -1
package/dist/core/config.d.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import * as z from "zod";
|
|
2
|
-
import { Diagnostic } from "./diagnostic";
|
|
3
|
-
import { PlVersion } from "./plVersion";
|
|
4
|
-
import { RuleId } from "./rules/registry";
|
|
5
|
-
/** Looked up in the course root, next to infoCourse.json. */
|
|
6
|
-
export declare const configFileName = ".pl-navigator.jsonc";
|
|
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
|
-
}>;
|
|
20
|
-
/** A glob and the directory it is relative to. */
|
|
21
|
-
interface Exclude {
|
|
22
|
-
base: string;
|
|
23
|
-
glob: string;
|
|
24
|
-
}
|
|
25
|
-
export interface NavigatorConfig {
|
|
26
|
-
plVersion: PlVersion;
|
|
27
|
-
/** Absolute. */
|
|
28
|
-
schemaCacheDir: string;
|
|
29
|
-
rules: Partial<Record<RuleId, RuleSetting>>;
|
|
30
|
-
/** Files never checked. Every source adds to this rather than replacing it. */
|
|
31
|
-
excludes: Exclude[];
|
|
32
|
-
}
|
|
33
|
-
/** Whether `file` matches one of the config's `excludes`. */
|
|
34
|
-
export declare function isExcluded(config: NavigatorConfig, file: string): boolean;
|
|
35
|
-
/** Same keys as the config file; from CLI flags or VS Code settings. */
|
|
36
|
-
export type ConfigOverrides = Partial<Record<keyof typeof fields, unknown>>;
|
|
37
|
-
/** A config problem, located in `file` when it came from one. */
|
|
38
|
-
export type ConfigProblem = Diagnostic & {
|
|
39
|
-
file?: string;
|
|
40
|
-
};
|
|
41
|
-
export interface LoadedConfig {
|
|
42
|
-
config: NavigatorConfig;
|
|
43
|
-
/** The config file that was read, if any. */
|
|
44
|
-
file?: string;
|
|
45
|
-
problems: ConfigProblem[];
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Reads `<courseRoot>/.pl-navigator.jsonc` (or `configPath`) and applies
|
|
49
|
-
* `overrides` on top. Invalid values are reported and replaced by defaults;
|
|
50
|
-
* this never throws.
|
|
51
|
-
*/
|
|
52
|
-
export declare function loadConfig(courseRoot: string | null, options?: {
|
|
53
|
-
configPath?: string;
|
|
54
|
-
overrides?: ConfigOverrides;
|
|
55
|
-
/** What relative paths in `overrides` are relative to (default: cwd). */
|
|
56
|
-
overridesBaseDir?: string;
|
|
57
|
-
defaults?: Partial<NavigatorConfig>;
|
|
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[];
|
|
80
|
-
export {};
|
package/dist/core/course.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { CourseId } from "../common";
|
|
2
|
-
/**
|
|
3
|
-
* Walks up from `filePath` to the nearest directory containing
|
|
4
|
-
* `infoCourse.json`. Returns that directory (the course id), or null.
|
|
5
|
-
* Pass the same `cache` across calls to look up each directory only once.
|
|
6
|
-
*/
|
|
7
|
-
export declare function findCourseRoot(filePath: string, cache?: Map<string, CourseId | null>): CourseId | null;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** Globs for each kind of PrairieLearn JSON file, relative to the course root. */
|
|
2
|
-
export declare const courseFiles: {
|
|
3
|
-
readonly course: "infoCourse.json";
|
|
4
|
-
readonly courseInstance: "courseInstances/**/infoCourseInstance.json";
|
|
5
|
-
readonly assessment: "courseInstances/**/assessments/**/infoAssessment.json";
|
|
6
|
-
readonly question: "questions/**/info.json";
|
|
7
|
-
readonly element: "elements/*/info.json";
|
|
8
|
-
readonly elementExtension: "elementExtensions/*/*/info.json";
|
|
9
|
-
};
|
|
10
|
-
export declare const questionHtml = "questions/**/question.html";
|
|
11
|
-
/** Matches a posix path relative to the course root against a glob. */
|
|
12
|
-
export declare function matchesGlob(relPath: string, glob: string): boolean;
|
|
13
|
-
/** `filePath` relative to `courseRoot`, with forward slashes. */
|
|
14
|
-
export declare function courseRelativePath(courseRoot: string, filePath: string): string;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Editor-agnostic diagnostic shared by the VS Code extension and the CLI.
|
|
3
|
-
* Ranges are character offsets into the checked text; adapters convert them
|
|
4
|
-
* to their own position types.
|
|
5
|
-
*/
|
|
6
|
-
type Severity = "error" | "warning" | "info";
|
|
7
|
-
interface RelatedInfo {
|
|
8
|
-
path: string;
|
|
9
|
-
message: string;
|
|
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
|
-
};
|
|
22
|
-
export interface Diagnostic {
|
|
23
|
-
startOffset: number;
|
|
24
|
-
endOffset: number;
|
|
25
|
-
message: string;
|
|
26
|
-
severity: Severity;
|
|
27
|
-
code?: string;
|
|
28
|
-
related?: RelatedInfo[];
|
|
29
|
-
metadata?: DiagnosticMetadata;
|
|
30
|
-
}
|
|
31
|
-
/** 1-based line/column for a character offset. */
|
|
32
|
-
export declare function offsetToPosition(text: string, offset: number): {
|
|
33
|
-
line: number;
|
|
34
|
-
column: number;
|
|
35
|
-
};
|
|
36
|
-
export {};
|
package/dist/core/html.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A minimal scanner for PrairieLearn element tags in question.html. It finds
|
|
3
|
-
* opening tags and their attributes with offsets; it is not a full HTML
|
|
4
|
-
* parser, but it skips comments and handles quoted values containing ">".
|
|
5
|
-
*/
|
|
6
|
-
interface HtmlAttribute {
|
|
7
|
-
value: string;
|
|
8
|
-
/** Offsets of the value, excluding quotes. */
|
|
9
|
-
startOffset: number;
|
|
10
|
-
endOffset: number;
|
|
11
|
-
}
|
|
12
|
-
export interface HtmlElement {
|
|
13
|
-
startOffset: number;
|
|
14
|
-
endOffset: number;
|
|
15
|
-
/** Lowercased attribute names. */
|
|
16
|
-
attributes: Map<string, HtmlAttribute>;
|
|
17
|
-
}
|
|
18
|
-
/** Opening tags of `tagName` (case-insensitive), in document order. */
|
|
19
|
-
export declare function findElements(text: string, tagName: string): HtmlElement[];
|
|
20
|
-
export {};
|
package/dist/core/json.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { JSONPath, Node } from "jsonc-parser";
|
|
2
|
-
/** A parsed JSON-with-comments document that keeps offsets for every node. */
|
|
3
|
-
export interface JsonDoc {
|
|
4
|
-
/** Undefined when the text has no parseable root value. */
|
|
5
|
-
value: unknown;
|
|
6
|
-
tree: Node | undefined;
|
|
7
|
-
errors: {
|
|
8
|
-
offset: number;
|
|
9
|
-
length: number;
|
|
10
|
-
message: string;
|
|
11
|
-
}[];
|
|
12
|
-
}
|
|
13
|
-
export declare function parseJsonDoc(text: string): JsonDoc;
|
|
14
|
-
/**
|
|
15
|
-
* Syntax errors under strict JSON, as PrairieLearn parses course files
|
|
16
|
-
* (JSON.parse): comments and trailing commas are errors.
|
|
17
|
-
*/
|
|
18
|
-
export declare function strictJsonErrors(text: string): JsonDoc["errors"];
|
|
19
|
-
/** Parses JSON with comments, returning undefined if it has syntax errors. */
|
|
20
|
-
export declare function parseJsonc(text: string): unknown;
|
|
21
|
-
/**
|
|
22
|
-
* Offsets of the value at `path` (or of its property name, with `key`),
|
|
23
|
-
* falling back to its nearest existing ancestor, then to the document start.
|
|
24
|
-
*/
|
|
25
|
-
export declare function rangeOf(doc: JsonDoc, path: JSONPath, options?: {
|
|
26
|
-
key?: boolean;
|
|
27
|
-
}): {
|
|
28
|
-
startOffset: number;
|
|
29
|
-
endOffset: number;
|
|
30
|
-
};
|
|
31
|
-
/** Converts a JSON pointer ("/a/0/b") into a path (["a", 0, "b"]). */
|
|
32
|
-
export declare function pointerToPath(pointer: string): JSONPath;
|
package/dist/core/plVersion.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PrairieLearn has no semver releases: it deploys continuously from `master`.
|
|
3
|
-
* A PL version is therefore identified by a date (the state of `master` at the
|
|
4
|
-
* end of that UTC day), a full commit sha, or "latest".
|
|
5
|
-
*/
|
|
6
|
-
export type IsoDate = string;
|
|
7
|
-
export type PlVersion = "latest" | IsoDate | string;
|
|
8
|
-
/**
|
|
9
|
-
* A space-separated conjunction of date comparisons, or "*".
|
|
10
|
-
* e.g. ">=2023-05-01 <2025-04-17"
|
|
11
|
-
*/
|
|
12
|
-
export type VersionRange = string;
|
|
13
|
-
export declare function isCommitSha(s: string): boolean;
|
|
14
|
-
/** Returns an error message if `s` is not a valid PL version. */
|
|
15
|
-
export declare function validatePlVersion(s: unknown): string | undefined;
|
|
16
|
-
export declare function today(): IsoDate;
|
|
17
|
-
/**
|
|
18
|
-
* The date used to select version-ranged rules. A sha's commit date is not
|
|
19
|
-
* known offline, so a pinned sha selects rules as if it were current.
|
|
20
|
-
*/
|
|
21
|
-
export declare function versionDate(version: PlVersion): IsoDate;
|
|
22
|
-
/** Whether `date` satisfies every comparator in `range`. */
|
|
23
|
-
export declare function matchesRange(range: VersionRange, date: IsoDate): boolean;
|
|
@@ -1,13 +0,0 @@
|
|
|
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[];
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { QuestionId } from "../common";
|
|
2
|
-
export type QuestionPaths = {
|
|
3
|
-
dir: string;
|
|
4
|
-
infoJson: string;
|
|
5
|
-
questionHtml: string;
|
|
6
|
-
serverPy: string;
|
|
7
|
-
};
|
|
8
|
-
export declare function questionFilePathsFromId(questionId: QuestionId): QuestionPaths & {
|
|
9
|
-
strict(): Partial<QuestionPaths>;
|
|
10
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { IsoDate } from "./plVersion";
|
|
2
|
-
import { FileRef } from "./references/extract";
|
|
3
|
-
/** One place a course file references a target. */
|
|
4
|
-
export interface Use {
|
|
5
|
-
/** The file holding the reference. */
|
|
6
|
-
file: string;
|
|
7
|
-
ref: FileRef;
|
|
8
|
-
/** 1-based, like offsetToPosition. */
|
|
9
|
-
start: {
|
|
10
|
-
line: number;
|
|
11
|
-
column: number;
|
|
12
|
-
};
|
|
13
|
-
end: {
|
|
14
|
-
line: number;
|
|
15
|
-
column: number;
|
|
16
|
-
};
|
|
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
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Reverse index of every file reference in one course: which files
|
|
27
|
-
* reference a given target. Also records the tags and assessment sets each
|
|
28
|
-
* file uses. Updated one file at a time.
|
|
29
|
-
*/
|
|
30
|
-
export declare class ReferenceIndex {
|
|
31
|
-
readonly courseRoot: string;
|
|
32
|
-
private plDate;
|
|
33
|
-
private usesByFile;
|
|
34
|
-
private namesByFile;
|
|
35
|
-
private filesByTarget;
|
|
36
|
-
/** How many targets lie under each directory, to answer directory events quickly. */
|
|
37
|
-
private targetsUnder;
|
|
38
|
-
constructor(courseRoot: string, plDate: IsoDate);
|
|
39
|
-
/** Whether `file` can hold references at all. */
|
|
40
|
-
holdsReferences(file: string): boolean;
|
|
41
|
-
/** Re-reads the references in `file` from `text`. */
|
|
42
|
-
update(file: string, text: string): void;
|
|
43
|
-
remove(file: string): void;
|
|
44
|
-
/** Every file holding references. */
|
|
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>;
|
|
50
|
-
/** References made by `file`. */
|
|
51
|
-
usesIn(file: string): Use[];
|
|
52
|
-
/**
|
|
53
|
-
* References to `target`. A reference to a directory (a question, or a
|
|
54
|
-
* serverFilesCourse folder) also counts as a use of every file inside it.
|
|
55
|
-
*/
|
|
56
|
-
usesOf(target: string): Use[];
|
|
57
|
-
private countUnder;
|
|
58
|
-
/**
|
|
59
|
-
* Files whose references can be affected by `changed` (a created, edited,
|
|
60
|
-
* or deleted path): references to it, to a directory containing it, or,
|
|
61
|
-
* when it is a directory, to anything inside it.
|
|
62
|
-
*/
|
|
63
|
-
filesAffectedBy(changed: string): string[];
|
|
64
|
-
}
|
|
65
|
-
/** Indexes every reference-holding file in a course from disk. */
|
|
66
|
-
export declare function indexCourse(courseRoot: string, plDate: IsoDate): ReferenceIndex;
|
|
67
|
-
/**
|
|
68
|
-
* Like indexCourse, but reads files asynchronously in batches, yielding
|
|
69
|
-
* between them, so a large course does not block an editor.
|
|
70
|
-
*/
|
|
71
|
-
export declare function indexCourseAsync(courseRoot: string, plDate: IsoDate, batchSize?: number): Promise<ReferenceIndex>;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/** Directories a reference can be relative to. */
|
|
2
|
-
export type BaseName = "fileDir" | "clientFilesQuestion" | "clientFilesCourse" | "serverFilesCourse" | "courseExtensions" | "questions";
|
|
3
|
-
export interface RefContext {
|
|
4
|
-
courseRoot: string;
|
|
5
|
-
/** Directory containing the file being read. */
|
|
6
|
-
fileDir: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function baseDir(base: BaseName, ctx: RefContext): string;
|
|
9
|
-
/**
|
|
10
|
-
* `base/value`, or null if that escapes `base`. Naming `base` itself counts
|
|
11
|
-
* as escaping unless `allowBase` (a directory may be the base; a file not).
|
|
12
|
-
*/
|
|
13
|
-
export declare function inside(base: string, value: string, allowBase?: boolean): string | null;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { JsonDoc } from "../json";
|
|
2
|
-
import { IsoDate } from "../plVersion";
|
|
3
|
-
import { RefContext } from "./bases";
|
|
4
|
-
import { RefSpec } from "./specs";
|
|
5
|
-
export interface FileRef {
|
|
6
|
-
spec: RefSpec;
|
|
7
|
-
value: string;
|
|
8
|
-
/** Offsets of the value, excluding quotes. */
|
|
9
|
-
startOffset: number;
|
|
10
|
-
endOffset: number;
|
|
11
|
-
/**
|
|
12
|
-
* Canonical absolute path the value names: the file, or for a question
|
|
13
|
-
* its directory. Null when the reference is invalid (see `problem`).
|
|
14
|
-
*/
|
|
15
|
-
target: string | null;
|
|
16
|
-
/** Why the reference is invalid regardless of the file system. */
|
|
17
|
-
problem?: string;
|
|
18
|
-
}
|
|
19
|
-
/** The file references are read from; `doc` is only read for JSON files. */
|
|
20
|
-
interface SourceFile {
|
|
21
|
-
text: string;
|
|
22
|
-
readonly doc: JsonDoc;
|
|
23
|
-
}
|
|
24
|
-
/** A SourceFile that parses its JSON on first use. */
|
|
25
|
-
export declare function sourceOf(text: string): SourceFile;
|
|
26
|
-
/** Whether any reference spec reads this course-relative path. */
|
|
27
|
-
export declare function holdsReferences(relPath: string): boolean;
|
|
28
|
-
/** All file references in a course file, in document order. */
|
|
29
|
-
export declare function findFileRefs(source: SourceFile, relPath: string, plDate: IsoDate, ctx: RefContext, filter?: (spec: RefSpec) => boolean): FileRef[];
|
|
30
|
-
/** The file reference whose value contains `offset`, if any. */
|
|
31
|
-
export declare function fileRefAt(refs: FileRef[], offset: number): FileRef | undefined;
|
|
32
|
-
/** The file to open for a reference: a question opens its html, else its info.json. */
|
|
33
|
-
export declare function openTarget(ref: FileRef): string | null;
|
|
34
|
-
export {};
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { VersionRange } from "../plVersion";
|
|
2
|
-
import { BaseName } from "./bases";
|
|
3
|
-
/**
|
|
4
|
-
* Every place a course file names another file that must exist. Each spec
|
|
5
|
-
* drives, from this one table:
|
|
6
|
-
* - an existence rule (`rule`, registered in rules/registry.ts),
|
|
7
|
-
* - editor jump-to-file, and
|
|
8
|
-
* - "uses" (the reverse index in ../referenceIndex.ts).
|
|
9
|
-
*
|
|
10
|
-
* Deliberately not listed:
|
|
11
|
-
* - nodeModules* / core* dependencies: files on the PrairieLearn server.
|
|
12
|
-
* - workspaceOptions.gradedFiles, and `file-name` on pl-image-capture,
|
|
13
|
-
* pl-file-editor, pl-file-upload, pl-rich-text-editor: student submissions.
|
|
14
|
-
* - type="dynamic" pl-figure / pl-file-download: generated by server.py.
|
|
15
|
-
*/
|
|
16
|
-
/** Matches a property key or array index; "*" matches any key or index. */
|
|
17
|
-
export type PathSegment = string | RegExp;
|
|
18
|
-
/**
|
|
19
|
-
* How an element's `directory` attribute picks the base directory.
|
|
20
|
-
* `named` maps attribute values to bases; with `relative`, any other value
|
|
21
|
-
* is a path relative to the question directory.
|
|
22
|
-
*/
|
|
23
|
-
interface DirectoryAttr {
|
|
24
|
-
default: string;
|
|
25
|
-
named: Readonly<Record<string, BaseName>>;
|
|
26
|
-
relative?: true;
|
|
27
|
-
}
|
|
28
|
-
interface JsonSource {
|
|
29
|
-
json: readonly PathSegment[];
|
|
30
|
-
base: BaseName;
|
|
31
|
-
}
|
|
32
|
-
interface HtmlSource {
|
|
33
|
-
tag: string;
|
|
34
|
-
attr: string;
|
|
35
|
-
/** Without one, values are relative to `base`. */
|
|
36
|
-
directory?: DirectoryAttr;
|
|
37
|
-
base?: BaseName;
|
|
38
|
-
/** Skip elements with type="dynamic" (generated files). */
|
|
39
|
-
staticOnly?: true;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* What must exist: a file, a directory, either, or a question (a directory
|
|
43
|
-
* under questions/ with info.json and, usually, question.html).
|
|
44
|
-
*/
|
|
45
|
-
export type TargetKind = "file" | "fileOrDir" | "question";
|
|
46
|
-
interface BaseSpec<Rule extends string> {
|
|
47
|
-
/** Stable id, used in tests and for debugging. */
|
|
48
|
-
id: string;
|
|
49
|
-
range: VersionRange;
|
|
50
|
-
/** Glob, relative to the course root, of files holding the reference. */
|
|
51
|
-
files: string;
|
|
52
|
-
rule: Rule;
|
|
53
|
-
target: TargetKind;
|
|
54
|
-
/** Severity of a missing target; defaults to error. */
|
|
55
|
-
severity?: "warning";
|
|
56
|
-
}
|
|
57
|
-
export type RefSpec<Rule extends string = string> = BaseSpec<Rule> & ({
|
|
58
|
-
source: "json";
|
|
59
|
-
json: JsonSource;
|
|
60
|
-
} | {
|
|
61
|
-
source: "html";
|
|
62
|
-
html: HtmlSource;
|
|
63
|
-
});
|
|
64
|
-
export declare const refSpecs: readonly [{
|
|
65
|
-
readonly source: "json";
|
|
66
|
-
readonly json: {
|
|
67
|
-
readonly json: readonly PathSegment[];
|
|
68
|
-
readonly base: BaseName;
|
|
69
|
-
};
|
|
70
|
-
readonly id: "question.dependencies.clientFilesCourse";
|
|
71
|
-
readonly range: "*";
|
|
72
|
-
readonly files: "questions/**/info.json";
|
|
73
|
-
readonly rule: "client-files-course-exist";
|
|
74
|
-
readonly target: "file";
|
|
75
|
-
}, {
|
|
76
|
-
readonly source: "json";
|
|
77
|
-
readonly json: {
|
|
78
|
-
readonly json: readonly PathSegment[];
|
|
79
|
-
readonly base: BaseName;
|
|
80
|
-
};
|
|
81
|
-
readonly id: "question.dependencies.clientFilesQuestion";
|
|
82
|
-
readonly range: "*";
|
|
83
|
-
readonly files: "questions/**/info.json";
|
|
84
|
-
readonly rule: "client-files-question-exist";
|
|
85
|
-
readonly target: "file";
|
|
86
|
-
}, {
|
|
87
|
-
readonly source: "json";
|
|
88
|
-
readonly json: {
|
|
89
|
-
readonly json: readonly PathSegment[];
|
|
90
|
-
readonly base: BaseName;
|
|
91
|
-
};
|
|
92
|
-
readonly id: "question.clientFiles";
|
|
93
|
-
readonly range: "*";
|
|
94
|
-
readonly files: "questions/**/info.json";
|
|
95
|
-
readonly rule: "client-files-question-exist";
|
|
96
|
-
readonly target: "file";
|
|
97
|
-
readonly severity: "warning";
|
|
98
|
-
}, {
|
|
99
|
-
readonly source: "json";
|
|
100
|
-
readonly json: {
|
|
101
|
-
readonly json: readonly PathSegment[];
|
|
102
|
-
readonly base: BaseName;
|
|
103
|
-
};
|
|
104
|
-
readonly id: "question.externalGradingOptions.serverFilesCourse";
|
|
105
|
-
readonly range: "*";
|
|
106
|
-
readonly files: "questions/**/info.json";
|
|
107
|
-
readonly rule: "server-files-course-exist";
|
|
108
|
-
readonly target: "fileOrDir";
|
|
109
|
-
}, {
|
|
110
|
-
readonly source: "json";
|
|
111
|
-
readonly json: {
|
|
112
|
-
readonly json: readonly PathSegment[];
|
|
113
|
-
readonly base: BaseName;
|
|
114
|
-
};
|
|
115
|
-
readonly id: "question.template";
|
|
116
|
-
readonly range: "*";
|
|
117
|
-
readonly files: "questions/**/info.json";
|
|
118
|
-
readonly rule: "question-exists";
|
|
119
|
-
readonly target: "question";
|
|
120
|
-
}, ...({
|
|
121
|
-
readonly source: "json";
|
|
122
|
-
readonly json: {
|
|
123
|
-
readonly json: readonly PathSegment[];
|
|
124
|
-
readonly base: BaseName;
|
|
125
|
-
};
|
|
126
|
-
readonly id: "elements/*/info.json.controller" | "elementExtensions/*/*/info.json.controller";
|
|
127
|
-
readonly range: "*";
|
|
128
|
-
readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
|
|
129
|
-
readonly rule: "element-files-exist";
|
|
130
|
-
readonly target: "file";
|
|
131
|
-
} | {
|
|
132
|
-
readonly source: "json";
|
|
133
|
-
readonly json: {
|
|
134
|
-
readonly json: readonly PathSegment[];
|
|
135
|
-
readonly base: BaseName;
|
|
136
|
-
};
|
|
137
|
-
readonly id: "elements/*/info.json.dependencies.element" | "elementExtensions/*/*/info.json.dependencies.element";
|
|
138
|
-
readonly range: "*";
|
|
139
|
-
readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
|
|
140
|
-
readonly rule: "element-files-exist";
|
|
141
|
-
readonly target: "file";
|
|
142
|
-
} | {
|
|
143
|
-
readonly source: "json";
|
|
144
|
-
readonly json: {
|
|
145
|
-
readonly json: readonly PathSegment[];
|
|
146
|
-
readonly base: BaseName;
|
|
147
|
-
};
|
|
148
|
-
readonly id: "elements/*/info.json.dynamicDependencies.element" | "elementExtensions/*/*/info.json.dynamicDependencies.element";
|
|
149
|
-
readonly range: "*";
|
|
150
|
-
readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
|
|
151
|
-
readonly rule: "element-files-exist";
|
|
152
|
-
readonly target: "file";
|
|
153
|
-
} | {
|
|
154
|
-
readonly source: "json";
|
|
155
|
-
readonly json: {
|
|
156
|
-
readonly json: readonly PathSegment[];
|
|
157
|
-
readonly base: BaseName;
|
|
158
|
-
};
|
|
159
|
-
readonly id: "elements/*/info.json.dependencies.clientFilesCourse" | "elementExtensions/*/*/info.json.dependencies.clientFilesCourse";
|
|
160
|
-
readonly range: "*";
|
|
161
|
-
readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
|
|
162
|
-
readonly rule: "client-files-course-exist";
|
|
163
|
-
readonly target: "file";
|
|
164
|
-
} | {
|
|
165
|
-
readonly source: "json";
|
|
166
|
-
readonly json: {
|
|
167
|
-
readonly json: readonly PathSegment[];
|
|
168
|
-
readonly base: BaseName;
|
|
169
|
-
};
|
|
170
|
-
readonly id: "elements/*/info.json.dynamicDependencies.clientFilesCourse" | "elementExtensions/*/*/info.json.dynamicDependencies.clientFilesCourse";
|
|
171
|
-
readonly range: "*";
|
|
172
|
-
readonly files: "elements/*/info.json" | "elementExtensions/*/*/info.json";
|
|
173
|
-
readonly rule: "client-files-course-exist";
|
|
174
|
-
readonly target: "file";
|
|
175
|
-
})[], {
|
|
176
|
-
readonly source: "json";
|
|
177
|
-
readonly json: {
|
|
178
|
-
readonly json: readonly PathSegment[];
|
|
179
|
-
readonly base: BaseName;
|
|
180
|
-
};
|
|
181
|
-
readonly id: "assessment.question";
|
|
182
|
-
readonly range: "*";
|
|
183
|
-
readonly files: "courseInstances/**/assessments/**/infoAssessment.json";
|
|
184
|
-
readonly rule: "question-exists";
|
|
185
|
-
readonly target: "question";
|
|
186
|
-
}, {
|
|
187
|
-
readonly source: "json";
|
|
188
|
-
readonly json: {
|
|
189
|
-
readonly json: readonly PathSegment[];
|
|
190
|
-
readonly base: BaseName;
|
|
191
|
-
};
|
|
192
|
-
readonly id: "assessment.alternative";
|
|
193
|
-
readonly range: "*";
|
|
194
|
-
readonly files: "courseInstances/**/assessments/**/infoAssessment.json";
|
|
195
|
-
readonly rule: "question-exists";
|
|
196
|
-
readonly target: "question";
|
|
197
|
-
}, {
|
|
198
|
-
readonly source: "html";
|
|
199
|
-
readonly html: HtmlSource;
|
|
200
|
-
readonly id: "pl-figure";
|
|
201
|
-
readonly range: "*";
|
|
202
|
-
readonly files: "questions/**/question.html";
|
|
203
|
-
readonly rule: "pl-figure-file-exist";
|
|
204
|
-
readonly target: "file";
|
|
205
|
-
}, {
|
|
206
|
-
readonly source: "html";
|
|
207
|
-
readonly html: HtmlSource;
|
|
208
|
-
readonly id: "pl-file-download";
|
|
209
|
-
readonly range: "*";
|
|
210
|
-
readonly files: "questions/**/question.html";
|
|
211
|
-
readonly rule: "pl-file-download-file-exist";
|
|
212
|
-
readonly target: "file";
|
|
213
|
-
}, {
|
|
214
|
-
readonly source: "html";
|
|
215
|
-
readonly html: HtmlSource;
|
|
216
|
-
readonly id: "pl-code";
|
|
217
|
-
readonly range: "*";
|
|
218
|
-
readonly files: "questions/**/question.html";
|
|
219
|
-
readonly rule: "pl-code-file-exist";
|
|
220
|
-
readonly target: "fileOrDir";
|
|
221
|
-
}, {
|
|
222
|
-
readonly source: "html";
|
|
223
|
-
readonly html: HtmlSource;
|
|
224
|
-
readonly id: "pl-file-editor";
|
|
225
|
-
readonly range: "*";
|
|
226
|
-
readonly files: "questions/**/question.html";
|
|
227
|
-
readonly rule: "pl-file-editor-file-exist";
|
|
228
|
-
readonly target: "file";
|
|
229
|
-
}, {
|
|
230
|
-
readonly source: "html";
|
|
231
|
-
readonly html: HtmlSource;
|
|
232
|
-
readonly id: "pl-graph";
|
|
233
|
-
readonly range: "*";
|
|
234
|
-
readonly files: "questions/**/question.html";
|
|
235
|
-
readonly rule: "pl-graph-file-exist";
|
|
236
|
-
readonly target: "file";
|
|
237
|
-
}, {
|
|
238
|
-
readonly source: "html";
|
|
239
|
-
readonly html: HtmlSource;
|
|
240
|
-
readonly id: "pl-rich-text-editor";
|
|
241
|
-
readonly range: "*";
|
|
242
|
-
readonly files: "questions/**/question.html";
|
|
243
|
-
readonly rule: "pl-rich-text-editor-file-exist";
|
|
244
|
-
readonly target: "file";
|
|
245
|
-
}, {
|
|
246
|
-
readonly source: "html";
|
|
247
|
-
readonly html: HtmlSource;
|
|
248
|
-
readonly id: "pl-excalidraw";
|
|
249
|
-
readonly range: "*";
|
|
250
|
-
readonly files: "questions/**/question.html";
|
|
251
|
-
readonly rule: "pl-excalidraw-file-exist";
|
|
252
|
-
readonly target: "file";
|
|
253
|
-
}, {
|
|
254
|
-
readonly source: "html";
|
|
255
|
-
readonly html: HtmlSource;
|
|
256
|
-
readonly id: "pl-xss-safe";
|
|
257
|
-
readonly range: "*";
|
|
258
|
-
readonly files: "questions/**/question.html";
|
|
259
|
-
readonly rule: "pl-xss-safe-file-exist";
|
|
260
|
-
readonly target: "fileOrDir";
|
|
261
|
-
}, {
|
|
262
|
-
readonly source: "html";
|
|
263
|
-
readonly html: HtmlSource;
|
|
264
|
-
readonly id: "pl-template";
|
|
265
|
-
readonly range: "*";
|
|
266
|
-
readonly files: "questions/**/question.html";
|
|
267
|
-
readonly rule: "pl-template-file-exist";
|
|
268
|
-
readonly target: "file";
|
|
269
|
-
}];
|
|
270
|
-
/** Ids of the existence rules generated from `refSpecs`. */
|
|
271
|
-
export type RefRuleId = (typeof refSpecs)[number]["rule"];
|
|
272
|
-
export {};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { SchemaName } from "../schemas";
|
|
2
|
-
import { RuleImpl } from "./types";
|
|
3
|
-
/**
|
|
4
|
-
* Reports JSON syntax errors. PrairieLearn reads course JSON with JSON.parse,
|
|
5
|
-
* so comments and trailing commas fail its sync. (Other rules still read
|
|
6
|
-
* such files tolerantly.)
|
|
7
|
-
*/
|
|
8
|
-
export declare const jsonSyntax: RuleImpl;
|
|
9
|
-
/** Validates the file against PrairieLearn's JSON schema for the PL version. */
|
|
10
|
-
export declare function schemaRule(name: SchemaName): RuleImpl;
|
|
11
|
-
/** Reports every reference governed by `rule` whose target is missing or invalid. */
|
|
12
|
-
export declare function referencesExist(rule: string): RuleImpl;
|
|
13
|
-
/** Warns on every occurrence of a question used more than once in a file. */
|
|
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;
|