Skip to content

Commit

Permalink
typescriptify
Browse files Browse the repository at this point in the history
  • Loading branch information
goto-bus-stop committed Jan 22, 2020
1 parent b5de27b commit 5f01073
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 55 deletions.
5 changes: 2 additions & 3 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"include": ["src/**/*.js"],
"include": ["src/*.ts", "dist/*.js"],
"lines": 100,
"branches": 100,
"statements": 100,
"functions": 100,
"reporter": ["lcov"],
"check-coverage": true,
"sourceMap": false,
"instrument": false
"sourceMap": true
}
22 changes: 5 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,22 @@
"author": "Renée Kooi <renee@kooi.me>",
"main": "dist/u-wave-parse-chat-markup.js",
"module": "dist/u-wave-parse-chat-markup.mjs",
"jsnext:main": "src/index.js",
"repository": "u-wave/parse-chat-markup",
"keywords": [
"u-wave"
],
"bugs": "https://github.com/u-wave/parse-chat-markup/issues",
"homepage": "https://github.com/u-wave/parse-chat-markup#readme",
"scripts": {
"prepublish": "npm run build",
"build": "rollup -c",
"test:lint": "eslint .",
"test:mocha": "cross-env BABEL_ENV=test mocha",
"cov": "nyc npm run test:mocha",
"test": "npm run cov && npm run test:lint"
"prepare": "rollup -c",
"test": "npm run prepare && nyc mocha"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/register": "^7.0.0",
"babel-plugin-istanbul": "^6.0.0",
"chai": "^4.1.2",
"cross-env": "^6.0.0",
"eslint": "^6.0.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.18.2",
"mocha": "^6.0.0",
"nyc": "^15.0.0",
"rollup": "^1.0.0",
"rollup-plugin-babel": "^4.0.2"
"rollup": "^1.29.1",
"rollup-plugin-typescript2": "^0.25.3",
"typescript": "^3.7.5"
}
}
6 changes: 3 additions & 3 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import babel from 'rollup-plugin-babel';
import typescript from 'rollup-plugin-typescript2';

const pkg = require('./package.json');

export default {
input: './src/index.js',
input: './src/index.ts',
output: [
{ format: 'cjs', file: pkg.main, exports: 'named' },
{ format: 'es', file: pkg.module },
],
plugins: [
babel(),
typescript(),
],
};
98 changes: 71 additions & 27 deletions src/index.js → src/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,67 @@
import urlRegExp from './url-regex';

function escapeStringRegExp(str) {
export type ItalicNode = {
type: 'italic',
content: MarkupNode[],
};
export type BoldNode = {
type: 'bold',
content: MarkupNode[],
};
export type CodeNode = {
type: 'code',
content: [string],
};
export type StrikeNode = {
type: 'strike',
content: MarkupNode[],
};
export type EmojiNode = {
type: 'emoji',
name: string,
};
export type MentionNode = {
type: 'mention',
mention: string,
raw: string,
};
export type LinkNode = {
type: 'link',
text: string,
href: string,
};

export type MarkupNode = string | ItalicNode | BoldNode | CodeNode | StrikeNode | EmojiNode | MentionNode | LinkNode;

export type MarkupOptions = {
/**
* The names of the available :emoji: shortcodes.
*/
emojiNames?: string[],
/**
* Usernames that can be mentioned.
*/
mentions?: string[],
};

function escapeStringRegExp(str: string) {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
}

function Token(type, text, raw = text) {
this.type = type;
this.text = text;
this.raw = raw;
interface Token {
type: string;
text: string;
raw: string;
};

function createToken(type: string, text: string, raw: string = text): Token {
return { type, text, raw};
}

/**
* Sort users by username length. Longest usernames first.
*
* @param {Array.<Object>} users
* @return {Array.<Object>}
*/

function sortMentions(mentions) {
function sortMentions(mentions: string[]): string[] {
return mentions.slice().sort((a, b) => b.length - a.length);
}

Expand All @@ -27,7 +71,7 @@ function sortMentions(mentions) {
* @param {string} mention Mentionable name.
* @return {RegExp}
*/
function mentionRegExp(mention) {
function mentionRegExp(mention: string): RegExp {
return new RegExp(`^${escapeStringRegExp(mention)}(?:\\b|\\s|\\W|$)`, 'i');
}

Expand All @@ -40,7 +84,7 @@ function mentionRegExp(mention) {
* @return {string|null} The correct emoji name (including casing), or `null` if
* the requested emoji does not exist.
*/
function findEmoji(names, match) {
function findEmoji(names: string[], match: string): string | null {
const compare = match.toLowerCase();
for (let i = 0; i < names.length; i += 1) {
const name = names[i].toLowerCase();
Expand All @@ -52,46 +96,46 @@ function findEmoji(names, match) {
return null;
}

function tokenize(text, opts) {
let chunk;
function tokenize(text: string, opts: MarkupOptions) {
let chunk: string;
let i = 0;
const mentions = sortMentions(opts.mentions || []);
const tokens = [];
const tokens: Token[] = [];
// adds a token of type `type` if the current chunk starts with
// a `delim`-delimited string
const delimited = (start, endRx, type) => {
const delimited = (start: string, endRx: RegExp, type: string) => {
if (chunk[0] === start && chunk[1] !== start) {
const end = 1 + chunk.slice(1).search(endRx);
if (end) {
tokens.push(new Token(type, chunk.slice(1, end)));
tokens.push(createToken(type, chunk.slice(1, end)));
i += end + 1;
return true;
}
}
return false;
};
const emoji = (type, emojiNames) => {
const emoji = (type: string, emojiNames?: string[]) => {
const match = /^:([A-Za-z0-9_+-]+):/.exec(chunk);
if (match) {
// if a whitelist of emoji names is given, only accept emoji from that
// list.
const emojiName = emojiNames ? findEmoji(emojiNames, match[1]) : match[1];
if (emojiName) {
tokens.push(new Token(type, emojiName, match[0]));
tokens.push(createToken(type, emojiName, match[0]));
i += match[0].length;
return true;
}
}
return false;
};
const mention = (start, type) => {
const mention = (start: string, type: string) => {
if (chunk[0] === start) {
const maybeMention = chunk.slice(1);
for (let mi = 0, ml = mentions.length; mi < ml; mi += 1) {
const candidate = mentions[mi];
if (mentionRegExp(candidate).test(maybeMention)) {
const end = candidate.length + 1;
tokens.push(new Token(type, chunk.slice(1, end), chunk.slice(0, end)));
tokens.push(createToken(type, chunk.slice(1, end), chunk.slice(0, end)));
i += end;
return true;
}
Expand All @@ -100,10 +144,10 @@ function tokenize(text, opts) {
return false;
};
const linkRx = new RegExp(`^${urlRegExp().source}`, 'i');
const link = (type) => {
const link = (type: string) => {
const match = linkRx.exec(chunk);
if (match) {
tokens.push(new Token(type, chunk.slice(0, match[0].length)));
tokens.push(createToken(type, chunk.slice(0, match[0].length)));
i += match[0].length;
return true;
}
Expand All @@ -114,7 +158,7 @@ function tokenize(text, opts) {
// .slice again because `i` changed
const m = /^\s+/.exec(text.slice(i));
if (m) {
tokens.push(new Token('word', m[0]));
tokens.push(createToken('word', m[0]));
i += m[0].length;
}
};
Expand All @@ -137,7 +181,7 @@ function tokenize(text, opts) {
if (tokens.length > 0 && tokens[tokens.length - 1].type === 'word') {
tokens[tokens.length - 1].text += chunk.slice(0, end);
} else {
tokens.push(new Token('word', chunk.slice(0, end)));
tokens.push(createToken('word', chunk.slice(0, end)));
}
i += end;
}
Expand All @@ -147,7 +191,7 @@ function tokenize(text, opts) {
return tokens;
}

function httpify(text) {
function httpify(text: string): string {
if (!/^[a-z]+:/.test(text)) {
return `http://${text}`;
}
Expand All @@ -157,7 +201,7 @@ function httpify(text) {
// Parses a chat message into a tree-ish structure.
// Options:
// * mentions: Names that can be mentioned.
export default function parse(message, opts = {}) {
export default function parse(message: string, opts: MarkupOptions = {}): MarkupNode[] {
if (typeof message !== 'string') {
throw new TypeError('Expected a string');
}
Expand Down
2 changes: 1 addition & 1 deletion src/url-regex.js → src/url-regex.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Adapted from https://github.com/kevva/url-regex.
*/
export default function urlRegex() {
export default function urlRegex(): RegExp {
const protocol = '(?:[a-z]+://)';
const auth = '(?:\\S+(?::\\S*)?@)?';
const host = '(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)';
Expand Down
6 changes: 3 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai';
import parseChatMarkup from '../src/index';
const { expect } = require('chai');
const parseChatMarkup = require('..').default;

describe('utils/parseChatMarkup', () => {
describe('parseChatMarkup', () => {
const bareOptions = {};

it('Only accepts string inputs', () => {
Expand Down
1 change: 0 additions & 1 deletion test/mocha.opts

This file was deleted.

13 changes: 13 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "es2015",
"target": "es5",
"declaration": true,
"newLine": "lf",
"strict": true,
"allowSyntheticDefaultImports": true
},
"include": [
"src/*.ts"
]
}

0 comments on commit 5f01073

Please sign in to comment.