From 1759f75c8668955e917666b5eb09a806a5eb58fd Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 23 Feb 2017 07:16:42 -0800 Subject: [PATCH] Merge pull request #1 from Microsoft/init Create package --- .../definitelytyped-header-parser/.gitignore | 38 +-- .../definitelytyped-header-parser/README.md | 7 + .../definitelytyped-header-parser/index.d.ts | 39 +++ .../definitelytyped-header-parser/index.ts | 266 ++++++++++++++++++ .../package.json | 30 ++ .../test/mocha-require.js | 5 + .../test/test.ts | 66 +++++ .../tsconfig.json | 16 ++ .../definitelytyped-header-parser/tslint.json | 13 + 9 files changed, 444 insertions(+), 36 deletions(-) create mode 100644 packages/definitelytyped-header-parser/index.d.ts create mode 100644 packages/definitelytyped-header-parser/index.ts create mode 100644 packages/definitelytyped-header-parser/package.json create mode 100644 packages/definitelytyped-header-parser/test/mocha-require.js create mode 100644 packages/definitelytyped-header-parser/test/test.ts create mode 100644 packages/definitelytyped-header-parser/tsconfig.json create mode 100644 packages/definitelytyped-header-parser/tslint.json diff --git a/packages/definitelytyped-header-parser/.gitignore b/packages/definitelytyped-header-parser/.gitignore index 5148e527..243e627b 100644 --- a/packages/definitelytyped-header-parser/.gitignore +++ b/packages/definitelytyped-header-parser/.gitignore @@ -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 diff --git a/packages/definitelytyped-header-parser/README.md b/packages/definitelytyped-header-parser/README.md index 07cd5097..d48243a5 100644 --- a/packages/definitelytyped-header-parser/README.md +++ b/packages/definitelytyped-header-parser/README.md @@ -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. diff --git a/packages/definitelytyped-header-parser/index.d.ts b/packages/definitelytyped-header-parser/index.d.ts new file mode 100644 index 00000000..f1e24f3a --- /dev/null +++ b/packages/definitelytyped-header-parser/index.d.ts @@ -0,0 +1,39 @@ +/// +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 = pm.Parser; + function seqMap(p1: Pr, p2: Pr, p3: Pr, p4: Pr, p5: Pr, p6: Pr, p7: Pr, p8: Pr, p9: Pr, cb: (a1: T, a2: U, a3: V, a4: W, a5: X, a6: Y, a7: Z, a8: A, a9: B) => C): Pr; +} diff --git a/packages/definitelytyped-header-parser/index.ts b/packages/definitelytyped-header-parser/index.ts new file mode 100644 index 00000000..3106712e --- /dev/null +++ b/packages/definitelytyped-header-parser/index.ts @@ -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 , Some Other Guy +// 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
{ + 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 = pm.regexp(/(, )|(,?\r?\n\/\/\s\s+)/); + +const projectParser: pm.Parser = pm.sepBy1(pm.regexp(/[^,\r\n]+/), separator); + +function contributorsParser(strict: boolean): pm.Parser { + 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, p9: Pr, + cb: (a1: T, a2: U, a3: V, a4: W, a5: X, a6: Y, a7: Z, a8: A, a9: B) => C): Pr; +} + +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; +} diff --git a/packages/definitelytyped-header-parser/package.json b/packages/definitelytyped-header-parser/package.json new file mode 100644 index 00000000..7d807eb6 --- /dev/null +++ b/packages/definitelytyped-header-parser/package.json @@ -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" +} diff --git a/packages/definitelytyped-header-parser/test/mocha-require.js b/packages/definitelytyped-header-parser/test/mocha-require.js new file mode 100644 index 00000000..9a1209ea --- /dev/null +++ b/packages/definitelytyped-header-parser/test/mocha-require.js @@ -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") +}); diff --git a/packages/definitelytyped-header-parser/test/test.ts b/packages/definitelytyped-header-parser/test/test.ts new file mode 100644 index 00000000..f0513470 --- /dev/null +++ b/packages/definitelytyped-header-parser/test/test.ts @@ -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 , Some Other Guy + // 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 , + // Some Other Guy + // 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"); +} diff --git a/packages/definitelytyped-header-parser/tsconfig.json b/packages/definitelytyped-header-parser/tsconfig.json new file mode 100644 index 00000000..111707e9 --- /dev/null +++ b/packages/definitelytyped-header-parser/tsconfig.json @@ -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"] +} \ No newline at end of file diff --git a/packages/definitelytyped-header-parser/tslint.json b/packages/definitelytyped-header-parser/tslint.json new file mode 100644 index 00000000..ee6fc1fe --- /dev/null +++ b/packages/definitelytyped-header-parser/tslint.json @@ -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 + } +} \ No newline at end of file