mirror of
https://github.com/chenasraf/DefinitelyTyped-tools.git
synced 2026-05-18 01:49:03 +00:00
@@ -1,37 +1,3 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
index.d.ts
|
||||
index.js
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# DefinitelyTyped Header Parser
|
||||
|
||||
This library parses headers of [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) types.
|
||||
|
||||
# Contributing
|
||||
|
||||
To build: `npm run build`.
|
||||
To test: `npm run test`. (Currently requires a re-build to test changes.)
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
39
packages/definitelytyped-header-parser/index.d.ts
vendored
Normal file
39
packages/definitelytyped-header-parser/index.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/// <reference types="parsimmon" />
|
||||
import pm = require("parsimmon");
|
||||
export declare type TypeScriptVersion = "2.0" | "2.1" | "2.2";
|
||||
export declare namespace TypeScriptVersion {
|
||||
const All: TypeScriptVersion[];
|
||||
const Lowest = "2.0";
|
||||
/** Latest version that may be specified in a `// TypeScript Version:` header. */
|
||||
const Latest = "2.2";
|
||||
/** True if a package with the given typescript version should be published as prerelease. */
|
||||
function isPrerelease(version: TypeScriptVersion): boolean;
|
||||
/** List of NPM tags that should be changed to point to the latest version. */
|
||||
function tagsToUpdate(typeScriptVersion: TypeScriptVersion): string[];
|
||||
}
|
||||
export interface Header {
|
||||
libraryName: string;
|
||||
libraryMajorVersion: number;
|
||||
libraryMinorVersion: number;
|
||||
typeScriptVersion: TypeScriptVersion;
|
||||
projects: string[];
|
||||
contributors: Author[];
|
||||
}
|
||||
export interface Author {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
export interface ParseError {
|
||||
index: number;
|
||||
line: number;
|
||||
column: number;
|
||||
expected: string[];
|
||||
}
|
||||
export declare function parseHeaderOrFail(mainFileContent: string): Header;
|
||||
export declare function validate(mainFileContent: string): ParseError | undefined;
|
||||
export declare function renderExpected(expected: string[]): string;
|
||||
export declare function parseTypeScriptVersionLine(line: string): TypeScriptVersion;
|
||||
declare module "parsimmon" {
|
||||
type Pr<T> = pm.Parser<T>;
|
||||
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>;
|
||||
}
|
||||
266
packages/definitelytyped-header-parser/index.ts
Normal file
266
packages/definitelytyped-header-parser/index.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import pm = require("parsimmon");
|
||||
|
||||
/*
|
||||
Example:
|
||||
// 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
|
||||
*/
|
||||
|
||||
export type TypeScriptVersion = "2.0" | "2.1" | "2.2";
|
||||
export namespace TypeScriptVersion {
|
||||
export const All: TypeScriptVersion[] = ["2.0", "2.1", "2.2"];
|
||||
export const Lowest = "2.0";
|
||||
/** Latest version that may be specified in a `// TypeScript Version:` header. */
|
||||
export const Latest = "2.2";
|
||||
|
||||
for (const v of All) {
|
||||
if (v > Latest) {
|
||||
throw new Error("'Latest' not properly set.");
|
||||
}
|
||||
}
|
||||
|
||||
/** True if a package with the given typescript version should be published as prerelease. */
|
||||
export function isPrerelease(version: TypeScriptVersion): boolean {
|
||||
return version === Latest;
|
||||
}
|
||||
|
||||
/** List of NPM tags that should be changed to point to the latest version. */
|
||||
export function tagsToUpdate(typeScriptVersion: TypeScriptVersion): string[] {
|
||||
switch (typeScriptVersion) {
|
||||
case "2.0":
|
||||
// A 2.0-compatible package is assumed compatible with TypeScript 2.1
|
||||
// We want the "2.1" tag to always exist.
|
||||
return [tags.latest, tags.v2_0, tags.v2_1, tags.v2_2, tags.v2_3];
|
||||
case "2.1":
|
||||
return [tags.latest, tags.v2_1, tags.v2_2, tags.v2_3];
|
||||
case "2.2":
|
||||
return [tags.latest, tags.v2_2, tags.v2_3];
|
||||
}
|
||||
}
|
||||
|
||||
namespace tags {
|
||||
export const latest = "latest";
|
||||
export const v2_0 = "ts2.0";
|
||||
export const v2_1 = "ts2.1";
|
||||
export const v2_2 = "ts2.2";
|
||||
export const v2_3 = "ts2.3";
|
||||
}
|
||||
}
|
||||
|
||||
export interface Header {
|
||||
libraryName: string;
|
||||
libraryMajorVersion: number;
|
||||
libraryMinorVersion: number;
|
||||
typeScriptVersion: TypeScriptVersion;
|
||||
projects: string[];
|
||||
contributors: Author[];
|
||||
}
|
||||
|
||||
export interface Author { name: string; url: string; }
|
||||
|
||||
export interface ParseError {
|
||||
index: number;
|
||||
line: number;
|
||||
column: number;
|
||||
expected: string[];
|
||||
}
|
||||
|
||||
export function parseHeaderOrFail(mainFileContent: string): Header {
|
||||
const header = parseHeader(mainFileContent, /*strict*/false);
|
||||
if (isParseError(header)) {
|
||||
throw new Error(renderParseError(header));
|
||||
}
|
||||
return header as Header;
|
||||
}
|
||||
|
||||
export function validate(mainFileContent: string): ParseError | undefined {
|
||||
const h = parseHeader(mainFileContent, /*strict*/true);
|
||||
return isParseError(h) ? h : undefined;
|
||||
}
|
||||
|
||||
export function renderExpected(expected: 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 {
|
||||
return !!(x as ParseError).expected;
|
||||
}
|
||||
|
||||
/** @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.string("// Type definitions for "),
|
||||
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,
|
||||
projects, contributors, typeScriptVersion
|
||||
}));
|
||||
}
|
||||
|
||||
interface Label { name: string; major: number; 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<string[]> = pm.sepBy1(pm.regexp(/[^,\r\n]+/), separator);
|
||||
|
||||
function contributorsParser(strict: boolean): pm.Parser<Author[]> {
|
||||
const contributor = pm.seqMap(pm.regexp(/([^<]+) /, 1), pm.regexp(/<([^>]+)>/, 1), (name, url) => ({ name, url }));
|
||||
const contributors = pm.sepBy1(contributor, separator);
|
||||
if (!strict) {
|
||||
// Allow trailing whitespace.
|
||||
return pm.seqMap(contributors, pm.regexp(/ */), a => a);
|
||||
}
|
||||
return contributors;
|
||||
};
|
||||
|
||||
// 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) {
|
||||
return fail();
|
||||
}
|
||||
const [, version, a, b, c, v, nameReverse] = match;
|
||||
|
||||
let majorReverse: string;
|
||||
let minorReverse: string;
|
||||
if (version) {
|
||||
if (c) {
|
||||
// There is a patch version
|
||||
majorReverse = c;
|
||||
minorReverse = b;
|
||||
if (strict) {
|
||||
return fail("patch version not allowed");
|
||||
}
|
||||
} else {
|
||||
majorReverse = b;
|
||||
minorReverse = a;
|
||||
}
|
||||
if (v && strict) {
|
||||
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.Result<Label> {
|
||||
let expected = "foo MAJOR.MINOR";
|
||||
if (msg) {
|
||||
expected += ` (${msg})`;
|
||||
}
|
||||
return pm.makeFailure(index, expected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const typeScriptVersionLineParser: pm.Parser<TypeScriptVersion> =
|
||||
pm.regexp(/\/\/ TypeScript Version: 2.(\d)/, 1).chain<TypeScriptVersion>(d => {
|
||||
switch (d) {
|
||||
case "1":
|
||||
return pm.succeed<TypeScriptVersion>("2.1");
|
||||
case "2":
|
||||
return pm.succeed<TypeScriptVersion>("2.2");
|
||||
default:
|
||||
return pm.fail(`TypeScript 2.${d} is not yet supported.`);
|
||||
}
|
||||
});
|
||||
|
||||
const typeScriptVersionParser: pm.Parser<TypeScriptVersion> =
|
||||
pm.regexp(/\r?\n/)
|
||||
.then(typeScriptVersionLineParser)
|
||||
.fallback<TypeScriptVersion>("2.0");
|
||||
|
||||
export function parseTypeScriptVersionLine(line: string): TypeScriptVersion {
|
||||
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" {
|
||||
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) {
|
||||
const n = Number.parseInt(str, 10);
|
||||
if (Number.isNaN(n)) {
|
||||
throw new Error(`Error in parseInt(${JSON.stringify(str)})`);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
30
packages/definitelytyped-header-parser/package.json
Normal file
30
packages/definitelytyped-header-parser/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "definitelytyped-header-parser",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"files": [
|
||||
"index.d.ts",
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"all": "npm-run-all build lint test",
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"lint": "tslint --format stylish index.ts test/*.ts",
|
||||
"test": "mocha --require test/mocha-require.js 'test/*.ts'"
|
||||
},
|
||||
"dependencies": {
|
||||
"parsimmon": "^1.2.0",
|
||||
"@types/parsimmon": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.39",
|
||||
"@types/node": "^7.0.5",
|
||||
"mocha": "^3.2.0",
|
||||
"npm-run-all": "^4.0.1",
|
||||
"ts-node": "^2.1.0",
|
||||
"tslint": "^4.4.2",
|
||||
"typescript": "^2.2.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const path = require("path");
|
||||
require("ts-node").register({
|
||||
project: path.join(__dirname, "../tsconfig.json"),
|
||||
compiler: path.join(__dirname, "../node_modules/typescript/lib/typescript.js")
|
||||
});
|
||||
66
packages/definitelytyped-header-parser/test/test.ts
Normal file
66
packages/definitelytyped-header-parser/test/test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import assert = require("assert");
|
||||
import { parseHeaderOrFail, parseTypeScriptVersionLine } from "..";
|
||||
|
||||
describe("parse", () => {
|
||||
it("works", () => {
|
||||
const src = dedent`
|
||||
// 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.2
|
||||
|
||||
...file content...`;
|
||||
assert.deepStrictEqual(parseHeaderOrFail(src), {
|
||||
libraryName: "foo",
|
||||
libraryMajorVersion: 1,
|
||||
libraryMinorVersion: 2,
|
||||
typeScriptVersion: "2.2",
|
||||
projects: ["https://github.com/foo/foo", "https://foo.com"],
|
||||
contributors: [
|
||||
{ name: "My Self", url: "https://github.com/me" },
|
||||
{ name: "Some Other Guy", url: "https://github.com/otherguy" }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it("works with spacing", () => {
|
||||
const src = dedent`
|
||||
// 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
|
||||
|
||||
...file content...`;
|
||||
|
||||
assert.deepStrictEqual(parseHeaderOrFail(src), {
|
||||
libraryName: "foo",
|
||||
libraryMajorVersion: 1,
|
||||
libraryMinorVersion: 2,
|
||||
typeScriptVersion: "2.0",
|
||||
projects: ["https://github.com/foo/foo", "https://foo.com"],
|
||||
contributors: [
|
||||
{ name: "My Self", url: "https://github.com/me" },
|
||||
{ name: "Some Other Guy", url: "https://github.com/otherguy" }
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseTypeScriptVersionLine", () => {
|
||||
it("works", () => {
|
||||
const src = "// TypeScript Version: 2.1";
|
||||
assert.equal(parseTypeScriptVersionLine(src), "2.1");
|
||||
|
||||
const wrong = "// TypeScript Version: 3.14";
|
||||
assert.throws(() => parseTypeScriptVersionLine(wrong));
|
||||
});
|
||||
});
|
||||
|
||||
function dedent(strings: TemplateStringsArray) {
|
||||
assert.equal(strings.length, 1);
|
||||
const x = strings[0].trim();
|
||||
return x.replace(/\n\t\t\t/g, "\n");
|
||||
}
|
||||
16
packages/definitelytyped-header-parser/tsconfig.json
Normal file
16
packages/definitelytyped-header-parser/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"declaration": true,
|
||||
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"files": ["index.ts"]
|
||||
}
|
||||
13
packages/definitelytyped-header-parser/tslint.json
Normal file
13
packages/definitelytyped-header-parser/tslint.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "tslint:latest",
|
||||
"rules": {
|
||||
"arrow-parens": false,
|
||||
"indent": [true, "tab"],
|
||||
"interface-name": false,
|
||||
"no-namespace": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"switch-default": false,
|
||||
"trailing-comma": false,
|
||||
"variable-name": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user