Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,16 @@
on first use, under `capitals` of the id, and in model space with the view's scale id after a
dimension style's: `DIMS-1in-10ft`. A view's id is taken when `view()` declares it, and the
one viewport that shows the view takes that id and no other.
- `lint(drawing, { standard })` runs the rules of a standard in `src/standard/lint.ts` after the
others, and without `standard` runs none of them. It takes the standard through `adopted` in
`src/standard/made.ts`, as the builder does, and reads only what `TYPED` in `src/shape.ts` finds
sound. A rule the builder refuses under a standard has the builder's rule name in `lint`, and
its message from `src/standard/drawn.ts` where that fits a `Drawing`. It must stay bounded by
construction: it reads a list up to its first empty slot, reads each item of the drawing once,
and pays for anything it does again, in proportion to its size, before it does it. The walk of
an insert reads blocks made once a drawing, with only what can draw a text and a short token for
each name, and pays for each block it enters. A title block's definition is compared as JSON
cut short at the length of the standard's. `test/standard-lint.test.ts` fuzzes it from a seed.
- `lint` takes any plain data and never throws, since a `Drawing` can be written by hand.
`src/shape.ts` holds the fields of each object and their types, once, for `fromJSON` and for
`lint`. `lint` checks an object's shape there before a rule reads it, and skips the rules for
Expand Down
93 changes: 93 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,96 @@ it would take `1e+100` for `1e+10` and report nothing.

`toDXF` throws a `DrawingError` for errors only. Its `diagnostics` property holds
them, and other code may throw one to refuse a drawing for its own reasons.

## A drawing's standard

`lint(drawing, { standard })` also holds a drawing to the standard it is drawn to, as the builder
does at each call, so a drawing made by other code or read from JSON is held to it too. AutoCAD
opens a drawing that breaks its standard, and `toDXF` writes one: these are a firm's rules, not
AutoCAD's. `lint` takes the standard as plain data, from any copy of tsdraft: it checks it, and
works out its hash again. A standard that does not check, or whose hash is not its content's, is
one `standard` error, and `lint` holds the drawing to nothing else of it. `discipline` names the
drawing's discipline, which resolves the entries with fields by discipline. Without `standard`,
`lint` checks none of this.

Errors:

- `layer-outside-standard`: a layer the standard does not have, and an entity of model space or
of a layout on layer `0`, which is in no standard. In a block, layer `0` draws as the insert's
layer does, and is the layer to draw on.
- `layer-differs-from-standard`: a layer whose color, linetype, lineweight or plot flag is not
the standard's, once for each.
- `linetype-outside-standard`: a linetype the standard does not have.
- `linetype-differs-from-standard`: a linetype whose pattern or description is not the
standard's, and a linetype scale other than the standard's.
- `pen-outside-standard`: an entity's lineweight that is no pen's. `ByLayer` and `ByBlock` are
every standard's.
- `text-style-outside-standard`: a text style that is no role's and no title block's, and a text
or an attribute definition in no role's text style, `Standard` among them.
- `text-style-differs-from-standard`: a role's text style whose font, width factor or oblique
angle is not the role's.
- `dimension-style-outside-standard`: a dimension style that is none of the standard's, which
are named after the style's id in capitals on a layout, such as `DIMS`, and with a scale's id
after it in model space, such as `DIMS-1in-10ft`.
- `dimension-style-differs-from-standard`: one of those whose text style, sizes, arrowhead,
scale or writing is not what the standard gives it, once for each. A style that the standard
says nothing about how to write in its units, the drawing's or the paper's, is
`dimension-style`.
- `scale-outside-standard`: a viewport whose ratio is no scale of the standard.
- `sheet-format-outside-standard`: a layout on a paper that no sheet format of the standard is
on, and one whose plot style table is not its sheet format's.
- `viewport-outside-drawing-area`: a viewport that reaches past the drawing area of its sheet
format's title block, or into the title block's box.
- `title-block-differs-from-standard`: a title block in the drawing that is not the standard's,
as below, and a layout that inserts the title block of another sheet format than its own.
- `height-off-role`: a text in a role's text style that plots at another height than the
role's.
- `height-below-standard`: a text that plots below the standard's `minTextHeight`.
- `dimension-off-scale`: a dimension shown in a viewport at another ratio than its style's
scale, and one on a layout in a style for model space.
- `sheet-per-file`: under `sheetFiles` `"sheet-per-file"`, a drawing with a second layout, and
a sheet number in the title block that cannot name a file.
- `layout`: under `sheetFiles` `"one-drawing"`, a sheet number in the title block that is not
its layout's name.
- `standard`: besides a standard that does not check, an R12 drawing, and a `discipline` that is
none of the standard's, or is given under `"one-drawing"`.

Warnings:

- `sheet-format-ambiguous`: a layout on a paper that several sheet formats are on, which inserts
the title block of none of them, or of several with other title blocks that its plot style
table does not tell apart. `lint` does not check its drawing area, its title block or its
fields.
- `title-block-missing`: a layout with no insert of its sheet format's title block.
- `title-block-field-unset`: a field of the title block, written as an attribute, that its insert
does not set, so it shows its attribute's default.
- `title-block-field-overflow`: a field's value that runs past the field's box, as `measureText`
measures it.
- `discipline-unknown`: the entries with fields by discipline that `lint` checked in none of
their forms, since it was not given the drawing's discipline.
- `standard-check-capped`: where the checks of text heights and dimension scales stopped. They
are bounded, counting each text, each dimension by the length of what its label reads, its
style's name, its own text and its style's prefix, suffix and text style, each insert they walk
and each item of the block it enters, each name they read the first time, by its length, and
each error by the length of its message. They enter only a block that can draw a text, so a
drawing with no text in its blocks costs nothing to walk. A drawing that draws more than they
look through, or nests blocks deeper than they can follow, gets one warning that says where
they stopped. The other checks take time in proportion to the drawing, the standard and the
problems they report: a standard given as data gets one problem for each pair of its names
that AutoCAD takes for one, however many there are.

A layout is on the one sheet format of the standard on its paper. Among several on its paper, it
is on those whose title block it inserts, and of those, on the ones whose plot style table it
has. When they share one title block, `lint` checks the layout against that title block.

A text is checked in each viewport that shows it, whether model space draws it itself or through
an insert: where its box meets the part of model space the viewport shows, and its layer is on,
thawed and plots. It plots at its height, times the scale of the insert that draws it, times the
viewport's scale. A text of a layout plots at its height, times its insert's scale. Heights are
compared as written, with 6 decimals.

A title block in the drawing is the standard's when every definition of its closure is, by its
JSON: its block, each block that block inserts, each text style their texts and attributes name,
each linetype their entities name, and its xref's name and path. Then the rules for pens, text
styles and role heights leave out what it draws, and `height-below-standard` still holds its
texts. When one of them differs, or is missing, every rule holds it as any other block.
21 changes: 20 additions & 1 deletion src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,34 @@ export function toJSON(drawing: Drawing): string {
* Writes a value as canonical JSON, as `toJSON` writes a drawing. Throws when the value holds
* one JSON cannot, such as `NaN`, naming it as `what` does. `written` may give the canonical
* JSON of an object already worked out, which is then used as it is.
*
* It also throws once the JSON would run past `most` characters, or it has read more than
* `most` keys, so a value that holds one list many times over costs no more than `most`.
*/
export function canonical(
value: unknown,
what = "A value",
written?: (value: object) => string | undefined,
most = Infinity,
): string {
let out = "";
// Every key read, those whose value is `undefined` among them, which are left out: so it counts
// the work, and is only an upper bound of the keys written.
let keys = 0;
const past = (more: number): void => {
if (out.length + more > most || keys > most) {
throw new RangeError(`${what} runs past ${most} characters of JSON`);
}
};
const write = (value: unknown): void => {
past(0);
switch (typeof value) {
case "number":
if (!Number.isFinite(value)) break;
out += Object.is(value, -0) ? "-0" : value;
return;
case "string":
past(value.length + 2);
out += JSON.stringify(value);
return;
case "boolean":
Expand All @@ -52,6 +66,8 @@ export function canonical(
return;
}
if (Array.isArray(value)) {
// Its brackets and commas.
past(value.length + 1);
out += "[";
for (let i = 0; i < value.length; i++) {
if (i > 0) out += ",";
Expand All @@ -61,8 +77,11 @@ export function canonical(
return;
}
const fields = value as Record<string, unknown>;
const names = Object.keys(fields);
keys += names.length;
past(0);
let first = true;
for (const key of Object.keys(fields).sort()) {
for (const key of names.sort()) {
if (fields[key] === undefined) continue;
out += `${first ? "{" : ","}${JSON.stringify(key)}:`;
first = false;
Expand Down
61 changes: 55 additions & 6 deletions src/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import { validIdHandle } from "./handles.ts";
import { validLayoutHandles, validViewportView } from "./layouts.ts";
import { paperInfo } from "./papers.ts";
import { isUnits } from "./units.ts";
import type { Standard } from "./standard/document.ts";
import { lintStandard } from "./standard/lint.ts";
import {
arrayProblem,
isObject,
Expand Down Expand Up @@ -165,11 +167,29 @@ export type LintRule =
| "dimension-style-outside-standard"
| "text-style-outside-standard"
| "sheet-format-outside-standard"
| "sheet-per-file";
| "sheet-per-file"
| "discipline-unknown"
| "linetype-differs-from-standard"
| "text-style-differs-from-standard"
| "dimension-style-differs-from-standard"
| "sheet-format-ambiguous"
| "height-below-standard"
| "height-off-role"
| "dimension-off-scale"
| "viewport-outside-drawing-area"
| "title-block-missing"
| "title-block-differs-from-standard"
| "title-block-field-unset"
| "title-block-field-overflow"
| "standard-check-capped";

export interface Diagnostic {
readonly rule: LintRule;
/** An error means AutoCAD rejects, repairs or garbles the output. `toDXF` refuses to write it. */
/**
* An error means AutoCAD rejects, repairs or garbles the output, and `toDXF` refuses to write
* it, or that the drawing breaks a rule of the standard `lint` was given, which `toDXF` does not
* check.
*/
readonly severity: "error" | "warning";
readonly message: string;
/** The layer the problem is in, when it is a layer problem. */
Expand Down Expand Up @@ -217,10 +237,31 @@ export interface Diagnostic {
readonly call?: CallSite;
}

export interface LintOptions {
/** The DXF version to check against. Defaults to the drawing's own. */
readonly version?: DxfVersion;
}
/**
* What `lint` checks a drawing against: its DXF version, and the standard it is drawn to, with
* the drawing's discipline.
*/
export type LintOptions =
| {
/** The DXF version to check against. Defaults to the drawing's own. */
readonly version?: DxfVersion;
readonly standard?: undefined;
readonly discipline?: undefined;
}
| {
/** The DXF version to check against. Defaults to the drawing's own. */
readonly version?: DxfVersion;
/**
* The standard the drawing is drawn to, as `defineStandard` returns it, from this copy of
* tsdraft or another. `lint` then holds the drawing to its rules too.
*/
readonly standard: Standard;
/**
* The drawing's discipline, a discipline of the standard, which resolves the entries with
* fields by discipline.
*/
readonly discipline?: string;
};

/**
* Checks a drawing against the rules AutoCAD enforces when it opens DXF.
Expand All @@ -234,6 +275,12 @@ export interface LintOptions {
* `Drawing`, such as a list that is not an array, an entity of an unknown kind or a field of
* the wrong type, is an error that gives the path to it, and no other rule is checked on that
* object. A field no object of its type has is an error too.
*
* With `standard`, it then holds the drawing to the standard: its layers, linetypes, pens, text
* styles, dimension styles, sheet formats and title blocks, the height each text plots at in
* each viewport that shows it and on its layout, and the scale each dimension is shown at. It
* takes the standard as plain data, checks it and works out its hash again, and a standard that
* does not check is one `standard` error. Without `standard` it checks none of this.
*/
export function lint(drawing: Drawing, options: LintOptions = {}): Diagnostic[] {
if (typeof drawing !== "object" || drawing === null || Array.isArray(drawing)) {
Expand Down Expand Up @@ -1211,6 +1258,8 @@ export function lint(drawing: Drawing, options: LintOptions = {}): Diagnostic[]
}
}

const given: unknown = options?.standard;
if (given !== undefined) lintStandard(drawing, given, options?.discipline, version, out);
return out;
}

Expand Down
3 changes: 2 additions & 1 deletion src/shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ function shapes(all: boolean, typed = all): Shapes {
let found = 0;
for (const key in value) {
const check = checks[key];
if (check === undefined) extra.push(unknownFieldProblem(at, key, names));
// `isSound` reads no message, and the hint compares the key with every field's name.
if (check === undefined) extra.push(asking ? "" : unknownFieldProblem(at, key, names));
else if (check !== LEFT) {
const field = value[key];
if (field === undefined) continue;
Expand Down
Loading
Loading