Skip to content

Commit 26ae40a

Browse files
committed
start on hat stats and golden tests
1 parent 1df6f02 commit 26ae40a

File tree

17 files changed

+1904
-0
lines changed

17 files changed

+1904
-0
lines changed

packages/common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@types/lodash": "4.14.181",
2121
"@types/mocha": "^8.0.4",
2222
"@types/sinon": "^10.0.2",
23+
"fast-check": "3.12.0",
2324
"js-yaml": "^4.1.0",
2425
"mocha": "^10.2.0",
2526
"sinon": "^11.1.1"

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,4 @@ export * from "./getFakeCommandServerApi";
8787
export * from "./types/TestCaseFixture";
8888
export * from "./util/getEnvironmentVariableStrict";
8989
export * from "./util/CompositeKeyDefaultMap";
90+
export { MockTextDocument, MockTextEditor } from "./testUtil/mockEditor";
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import * as assert from "assert";
2+
import { MockTextDocument, Range } from "..";
3+
import * as fc from "fast-check";
4+
5+
suite("mockEditor", () => {
6+
test("basic", () => {
7+
const s = "abc\n\n123\n";
8+
const doc: MockTextDocument = new MockTextDocument(
9+
"test.txt",
10+
"plaintext",
11+
s,
12+
);
13+
14+
for (let i = 0; i < s.length; i++) {
15+
const pos = doc.positionAt(i);
16+
const offset = doc.offsetAt(pos);
17+
assert.equal(offset, i);
18+
}
19+
const line0 = doc.lineAt(0);
20+
assert.equal(line0.text, "abc");
21+
assert.equal(line0.firstNonWhitespaceCharacterIndex, 0);
22+
assert.equal(line0.isEmptyOrWhitespace, false);
23+
assert.equal(line0.lineNumber, 0);
24+
assert.ok(line0.range.isEqual(new Range(0, 0, 0, 3)));
25+
assert.equal(line0.rangeIncludingLineBreak.start.character, 0);
26+
assert.equal(line0.lastNonWhitespaceCharacterIndex, 2);
27+
28+
const line1 = doc.lineAt(1);
29+
assert.equal(line1.text, "");
30+
assert.equal(line1.firstNonWhitespaceCharacterIndex, 0);
31+
assert.equal(line1.isEmptyOrWhitespace, true);
32+
assert.equal(line1.lineNumber, 1);
33+
assert.ok(line1.range.isEqual(new Range(1, 0, 1, 0)));
34+
assert.equal(line1.rangeIncludingLineBreak.start.character, 0);
35+
assert.equal(line1.lastNonWhitespaceCharacterIndex, 0);
36+
});
37+
38+
test("fastcheck", () => {
39+
fc.assert(
40+
fc.property(fc.string(), (contents) => {
41+
const doc: MockTextDocument = new MockTextDocument(
42+
"test.txt",
43+
"plaintext",
44+
contents,
45+
);
46+
let tot: number = 0;
47+
for (let lineno = 0; lineno < doc.lineCount; lineno++) {
48+
const line = doc.lineAt(lineno);
49+
tot += line.rangeIncludingLineBreak.end.character;
50+
assert.equal(line.lineNumber, lineno);
51+
assert.equal(line.range.start.line, lineno);
52+
assert.equal(line.range.end.line, lineno);
53+
assert.equal(line.rangeIncludingLineBreak.start.line, lineno);
54+
assert.equal(line.rangeIncludingLineBreak.end.line, lineno);
55+
assert.equal(
56+
line.rangeIncludingLineBreak.end.character,
57+
line.text.length,
58+
);
59+
assert.equal(
60+
line.rangeIncludingLineBreak.end.character,
61+
line.range.end.character,
62+
);
63+
}
64+
assert.equal(tot, contents.length);
65+
66+
for (let i = 0; i < contents.length; i++) {
67+
const pos = doc.positionAt(i);
68+
// positions must be within the range of a line
69+
assert.ok(pos.character <= doc.lineAt(pos.line).range.end.character);
70+
const offset = doc.offsetAt(pos);
71+
// positionAt and offsetAt are inverses
72+
assert.equal(offset, i);
73+
return true;
74+
}
75+
}),
76+
);
77+
});
78+
});
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { URI } from "vscode-uri";
2+
import {
3+
EndOfLine,
4+
Position,
5+
Range,
6+
Selection,
7+
TextDocument,
8+
TextEditor,
9+
TextEditorOptions,
10+
TextLine,
11+
} from "..";
12+
13+
// See the TextLine, TextEditor, and TextDocument interfaces
14+
// for documentation of these classes and their fields.
15+
16+
export class MockTextLine implements TextLine {
17+
readonly lineNumber: number;
18+
readonly text: string;
19+
readonly range: Range;
20+
readonly rangeIncludingLineBreak: Range;
21+
readonly firstNonWhitespaceCharacterIndex: number;
22+
readonly lastNonWhitespaceCharacterIndex: number;
23+
readonly isEmptyOrWhitespace: boolean;
24+
25+
constructor(lineNumber: number, text: string) {
26+
if (lineNumber < 0) {
27+
throw new Error("lineNumber must be non-negative");
28+
}
29+
this.lineNumber = lineNumber;
30+
// capture any trailing \r\n or \n as eol (possibly neither is present)
31+
const eol = text.match(/(\r?\n)$/)?.[1] ?? "";
32+
if (eol.length > 0) {
33+
this.text = text.slice(0, -eol.length);
34+
} else {
35+
this.text = text;
36+
}
37+
this.range = new Range(
38+
this.lineNumber,
39+
0,
40+
this.lineNumber,
41+
this.text.length,
42+
);
43+
this.rangeIncludingLineBreak = new Range(
44+
this.lineNumber,
45+
0,
46+
this.lineNumber,
47+
this.text.length + eol.length,
48+
);
49+
const first = this.text.search(/\S/);
50+
this.firstNonWhitespaceCharacterIndex =
51+
first === -1 ? this.text.length : first;
52+
const all = this.text.match(/\S/g);
53+
this.lastNonWhitespaceCharacterIndex = all
54+
? this.text.lastIndexOf(all[all.length - 1])
55+
: 0;
56+
this.isEmptyOrWhitespace =
57+
this.firstNonWhitespaceCharacterIndex === this.text.length;
58+
}
59+
}
60+
61+
export class MockTextDocument implements TextDocument {
62+
readonly uri: URI;
63+
readonly languageId: string;
64+
readonly version: number;
65+
readonly range: Range;
66+
readonly eol: EndOfLine;
67+
private lines: MockTextLine[];
68+
private contents: string;
69+
70+
constructor(filename: string, languageId: string, contents: string) {
71+
this.uri = URI.file(filename);
72+
this.languageId = languageId;
73+
this.version = 1;
74+
this.contents = contents;
75+
const rawLines: string[] = contents.match(/[^\n]*\n|[^\n]+/g) ?? [];
76+
this.lines = rawLines.map((line, i) => {
77+
return new MockTextLine(i, line);
78+
});
79+
if (this.lines.length === 0) {
80+
this.range = new Range(0, 0, 0, 0);
81+
} else {
82+
const lastLineRange = this.lines[this.lines.length - 1].range;
83+
this.range = new Range(
84+
0,
85+
0,
86+
lastLineRange.end.line,
87+
lastLineRange.end.character,
88+
);
89+
}
90+
this.eol = "LF";
91+
}
92+
93+
get lineCount(): number {
94+
return this.lines.length;
95+
}
96+
97+
public lineAt(x: number | Position): TextLine {
98+
if (typeof x === "number") {
99+
return this.lines[x];
100+
}
101+
return this.lines[x.line];
102+
}
103+
104+
public offsetAt(position: Position): number {
105+
let offset = 0;
106+
for (let i = 0; i < position.line; i++) {
107+
offset += this.lineAt(i).rangeIncludingLineBreak.end.character;
108+
}
109+
offset += position.character;
110+
return offset;
111+
}
112+
113+
public positionAt(offset: number): Position {
114+
let line = 0;
115+
while (offset >= this.lineAt(line).rangeIncludingLineBreak.end.character) {
116+
offset -= this.lineAt(line).rangeIncludingLineBreak.end.character;
117+
line++;
118+
}
119+
return new Position(line, offset);
120+
}
121+
122+
public getText(range?: Range): string {
123+
if (range === undefined) {
124+
return this.contents;
125+
}
126+
const startOffset = this.offsetAt(range.start);
127+
const endOffset = this.offsetAt(range.end);
128+
return this.contents.slice(startOffset, endOffset);
129+
}
130+
}
131+
132+
export class MockTextEditor implements TextEditor {
133+
public primarySelection: Selection;
134+
readonly id: string;
135+
readonly document: TextDocument;
136+
readonly options: TextEditorOptions;
137+
readonly isActive: boolean;
138+
139+
constructor(document: TextDocument, active: boolean) {
140+
this.id = document.uri.toString();
141+
this.document = document;
142+
this.primarySelection = new Selection(0, 0, 0, 0);
143+
this.options = new DefaultTextEditorOptions();
144+
this.isActive = active;
145+
// TODO: support visible ranges, multiple selections, options
146+
}
147+
148+
get visibleRanges(): Range[] {
149+
return [this.document.range];
150+
}
151+
152+
get selections(): Selection[] {
153+
return [this.primarySelection];
154+
}
155+
156+
isEqual(other: TextEditor): boolean {
157+
return this.id === other.id;
158+
}
159+
}
160+
161+
class DefaultTextEditorOptions implements TextEditorOptions {
162+
get tabSize(): number | string {
163+
return 4;
164+
}
165+
166+
get insertSpaces(): boolean | string {
167+
return true;
168+
}
169+
}

packages/common/src/types/Range.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ export class Range {
6464
return this.start.isEqual(this.end);
6565
}
6666

67+
/**
68+
* Check if this range is equal to `other`.
69+
*
70+
* @param other A range.
71+
* @return `true` if the start and end of the given range are equal to
72+
* the start and end of this range.
73+
*/
74+
public isEqual(other: Range): boolean {
75+
return this.start.isEqual(other.start) && this.end.isEqual(other.end);
76+
}
77+
6778
/**
6879
* `true` if `start.line` and `end.line` are equal.
6980
*/

0 commit comments

Comments
 (0)