This repository was archived by the owner on Nov 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathindex.ts
349 lines (295 loc) · 12.2 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
import assert = require("assert");
import pm = require("parsimmon");
/**
# Example header format #
// Type definitions for foo 1.2
// Project: https://github.com/foo/foo, https://foo.com
// Definitions by: My Self <https://github.com/me>, Some Other Guy <https://github.com/otherguy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.1
# How to add new version of Typescript #
For the RC:
1. Add a new version to the end of `supportedTags`.
2. Update failing tests.
3. Publish and update dependents.
For the release:
1. Add new versions to the end of `TypeScriptVersion` and `supported`.
2. Update failing tests.
3. Publish and update dependents.
# How to deprecate versions on Definitely Typed #
1. Move versions from `TypeScriptVersion` to `UnsupportedTypeScriptVersion`.
2. Move versions from `supported` to `unsupported`.
3. Remove entry from `supportedTags`.
4. Update failing tests.
5. Publish and update dependents.
*/
/** Parseable but unsupported TypeScript versions. */
export type UnsupportedTypeScriptVersion =
| "2.0" | "2.1" | "2.2" | "2.3" | "2.4" | "2.5" | "2.6" | "2.7";
/**
* Parseable and supported TypeScript versions.
* Only add to this list if we will support this version on DefinitelyTyped.
*/
export type TypeScriptVersion =
| "2.8" | "2.9"
| "3.0" | "3.1" | "3.2" | "3.3" | "3.4" | "3.5" | "3.6" | "3.7" | "3.8" | "3.9";
export type AllTypeScriptVersion = UnsupportedTypeScriptVersion | TypeScriptVersion;
export namespace TypeScriptVersion {
export const supported: readonly TypeScriptVersion[] =
["2.8", "2.9",
"3.0", "3.1", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9"];
export const unsupported: readonly UnsupportedTypeScriptVersion[] =
["2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7"];
export const all: readonly AllTypeScriptVersion[] = [...unsupported, ...supported];
export const lowest = supported[0];
/** Latest version that may be specified in a `// TypeScript Version:` header. */
export const latest = supported[supported.length - 1];
/** @deprecated */
export function isPrerelease(_version: TypeScriptVersion): boolean {
return false;
}
export function isSupported(v: AllTypeScriptVersion): v is TypeScriptVersion {
return supported.indexOf(v as TypeScriptVersion) > -1;
}
export function range(min: TypeScriptVersion): ReadonlyArray<TypeScriptVersion> {
return supported.filter(v => v >= min);
}
const supportedTags: readonly string[] = [
"ts2.8",
"ts2.9",
"ts3.0",
"ts3.1",
"ts3.2",
"ts3.3",
"ts3.4",
"ts3.5",
"ts3.6",
"ts3.7",
"ts3.8",
"ts3.9",
"latest",
];
/** List of NPM tags that should be changed to point to the latest version. */
export function tagsToUpdate(v: TypeScriptVersion): ReadonlyArray<string> {
const idx = supportedTags.indexOf(`ts${v}`);
assert(idx !== -1);
return supportedTags.slice(idx);
}
export function previous(v: TypeScriptVersion): TypeScriptVersion | undefined {
const index = supported.indexOf(v);
assert(index !== -1);
return index === 0 ? undefined : supported[index - 1];
}
export function isRedirectable(v: TypeScriptVersion): boolean {
return all.indexOf(v) >= all.indexOf("3.1");
}
}
export interface Header {
readonly nonNpm: boolean;
readonly libraryName: string;
readonly libraryMajorVersion: number;
readonly libraryMinorVersion: number;
readonly typeScriptVersion: AllTypeScriptVersion;
readonly projects: ReadonlyArray<string>;
readonly contributors: ReadonlyArray<Author>;
}
export interface Author {
readonly name: string;
readonly url: string;
readonly githubUsername: string | undefined;
}
export interface ParseError {
readonly index: number;
readonly line: number;
readonly column: number;
readonly expected: ReadonlyArray<string>;
}
export function isTypeScriptVersion(str: string): str is TypeScriptVersion {
return TypeScriptVersion.all.includes(str as TypeScriptVersion);
}
export function makeTypesVersionsForPackageJson(typesVersions: ReadonlyArray<TypeScriptVersion>): unknown {
if (typesVersions.length === 0) { return undefined; }
const out: { [key: string]: { readonly "*": ReadonlyArray<string> } } = {};
for (const version of typesVersions) {
out[`>=${version}.0-0`] = { "*": [`ts${version}/*`] };
}
return out;
}
export function parseHeaderOrFail(mainFileContent: string): Header {
const header = parseHeader(mainFileContent, /*strict*/false);
if (isParseError(header)) {
throw new Error(renderParseError(header));
}
return header;
}
export function validate(mainFileContent: string): ParseError | undefined {
const h = parseHeader(mainFileContent, /*strict*/true);
return isParseError(h) ? h : undefined;
}
export function renderExpected(expected: ReadonlyArray<string>): string {
return expected.length === 1 ? expected[0] : `one of\n\t${expected.join("\n\t")}`;
}
function renderParseError({ line, column, expected }: ParseError): string {
return `At ${line}:${column} : Expected ${renderExpected(expected)}`;
}
function isParseError(x: {}): x is ParseError {
// tslint:disable-next-line strict-type-predicates
return (x as ParseError).expected !== undefined;
}
/** @param strict If true, we allow fewer things to be parsed. Turned on by linting. */
function parseHeader(text: string, strict: boolean): Header | ParseError {
const res = headerParser(strict).parse(text);
return res.status
? res.value
: { index: res.index.offset, line: res.index.line, column: res.index.column, expected: res.expected };
}
function headerParser(strict: boolean): pm.Parser<Header> {
return pm.seqMap(
pm.regex(/\/\/ Type definitions for (non-npm package )?/),
parseLabel(strict),
pm.string("// Project: "),
projectParser,
pm.regexp(/\r?\n\/\/ Definitions by: /),
contributorsParser(strict),
definitionsParser,
typeScriptVersionParser,
pm.all, // Don't care about the rest of the file
// tslint:disable-next-line:variable-name
(str, label, _project, projects, _defsBy, contributors, _definitions, typeScriptVersion) => ({
libraryName: label.name,
libraryMajorVersion: label.major,
libraryMinorVersion: label.minor,
nonNpm: str.endsWith("non-npm package "),
projects, contributors, typeScriptVersion,
}));
}
interface Label {
readonly name: string;
readonly major: number;
readonly minor: number;
}
/*
Allow any of the following:
// Project: https://foo.com
// https://bar.com
// Project: https://foo.com,
// https://bar.com
// Project: https://foo.com, https://bar.com
Use `\s\s+` to ensure at least 2 spaces, to disambiguate from the next line being `// Definitions by:`.
*/
const separator: pm.Parser<string> = pm.regexp(/(, )|(,?\r?\n\/\/\s\s+)/);
const projectParser: pm.Parser<ReadonlyArray<string>> = pm.sepBy1(pm.regexp(/[^,\r\n]+/), separator);
function contributorsParser(strict: boolean): pm.Parser<ReadonlyArray<Author>> {
const contributor: pm.Parser<Author> = strict
? pm.seqMap(
pm.regexp(/([^<]+) /, 1),
pm.regexp(/\<https\:\/\/github\.com\/([a-zA-Z\d\-]+)\/?\>/, 1),
(name, githubUsername) => ({ name, url: `https://github.com/${githubUsername}`, githubUsername }))
// In non-strict mode, allows arbitrary URL, and trailing whitespace.
: pm.seqMap(pm.regexp(/([^<]+) /, 1), pm.regexp(/<([^>]+)> */, 1), (name, url) => {
const rgx = /^https\:\/\/github.com\/([a-zA-Z\d\-]+)\/?$/;
const match = rgx.exec(url);
const githubUsername = match === null ? undefined : match[1];
// tslint:disable-next-line no-null-keyword
return ({ name, url: githubUsername ? `https://github.com/${githubUsername}` : url, githubUsername });
});
return pm.sepBy1(contributor, separator);
}
// TODO: Should we do something with the URL?
const definitionsParser = pm.regexp(/\r?\n\/\/ Definitions: [^\r\n]+/);
function parseLabel(strict: boolean): pm.Parser<Label> {
return pm.Parser((input, index) => {
// Take all until the first newline.
const endIndex = regexpIndexOf(input, /\r|\n/, index);
if (endIndex === -1) {
return fail("EOF");
}
// Index past the end of the newline.
const end = input[endIndex] === "\r" ? endIndex + 2 : endIndex + 1;
const tilNewline = input.slice(index, endIndex);
// Parse in reverse. Once we've stripped off the version, the rest is the libary name.
const reversed = reverse(tilNewline);
// Last digit is allowed to be "x", which acts like "0"
const rgx = /((\d+|x)\.(\d+)(\.\d+)?(v)? )?(.+)/;
const match = rgx.exec(reversed);
if (match === null) { // tslint:disable-line no-null-keyword
return fail();
}
const [, version, a, b, c, v, nameReverse] = match;
let majorReverse: string;
let minorReverse: string;
if (version !== undefined) { // tslint:disable-line strict-type-predicates
if (c !== undefined) { // tslint:disable-line strict-type-predicates
// There is a patch version
majorReverse = c;
minorReverse = b;
if (strict) {
return fail("patch version not allowed");
}
} else {
majorReverse = b;
minorReverse = a;
}
if (v !== undefined && strict) { // tslint:disable-line strict-type-predicates
return fail("'v' not allowed");
}
} else {
if (strict) {
return fail("Needs MAJOR.MINOR");
}
majorReverse = "0"; minorReverse = "0";
}
const [name, major, minor] = [reverse(nameReverse), reverse(majorReverse), reverse(minorReverse)];
return pm.makeSuccess<Label>(
end,
{ name, major: intOfString(major), minor: minor === "x" ? 0 : intOfString(minor) });
function fail(msg?: string): pm.Reply<Label> {
let expected = "foo MAJOR.MINOR";
if (msg !== undefined) {
expected += ` (${msg})`;
}
return pm.makeFailure(index, expected);
}
});
}
const typeScriptVersionLineParser: pm.Parser<AllTypeScriptVersion> =
pm.regexp(/\/\/ (?:Minimum )?TypeScript Version: (\d.(\d))/, 1).chain<TypeScriptVersion>(v =>
TypeScriptVersion.all.includes(v as TypeScriptVersion)
? pm.succeed(v as TypeScriptVersion)
: pm.fail(`TypeScript ${v} is not yet supported.`));
const typeScriptVersionParser: pm.Parser<AllTypeScriptVersion> =
pm.regexp(/\r?\n/)
.then(typeScriptVersionLineParser)
.fallback<TypeScriptVersion>("2.8");
export function parseTypeScriptVersionLine(line: string): AllTypeScriptVersion {
const result = typeScriptVersionLineParser.parse(line);
if (!result.status) {
throw new Error(`Could not parse version: line is '${line}'`);
}
return result.value;
}
function reverse(s: string): string {
let out = "";
for (let i = s.length - 1; i >= 0; i--) {
out += s[i];
}
return out;
}
function regexpIndexOf(s: string, rgx: RegExp, start: number): number {
const index = s.slice(start).search(rgx);
return index === -1 ? index : index + start;
}
declare module "parsimmon" {
// tslint:disable-next-line no-unnecessary-qualifier
type Pr<T> = pm.Parser<T>; // https://github.com/Microsoft/TypeScript/issues/14121
export function seqMap<T, U, V, W, X, Y, Z, A, B, C>(
p1: Pr<T>, p2: Pr<U>, p3: Pr<V>, p4: Pr<W>, p5: Pr<X>, p6: Pr<Y>, p7: Pr<Z>, p8: Pr<A>, p9: Pr<B>,
cb: (a1: T, a2: U, a3: V, a4: W, a5: X, a6: Y, a7: Z, a8: A, a9: B) => C): Pr<C>;
}
function intOfString(str: string): number {
const n = Number.parseInt(str, 10);
if (Number.isNaN(n)) {
throw new Error(`Error in parseInt(${JSON.stringify(str)})`);
}
return n;
}