Skip to content

Commit fbc8c3a

Browse files
dbkrNick Frasser
authored andcommitted
Links in angle brackets (#166)
* Treat URLs in angle brackets same as parens Make "<http://example.com>" parse the same as "(http://example.com)" by adding angle bracket versions of the existing logic for braces, brackets and parentheses. This breaks the indenting in parser.js: I'll fix this in a separate commit to make the diffs legible. * Whitespace Move the RHS of the assignments over to match the angle bracket lines.
1 parent 2961b02 commit fbc8c3a

File tree

5 files changed

+65
-29
lines changed

5 files changed

+65
-29
lines changed

src/linkify/core/parser.js

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ import {
3535
TLD,
3636
OPENBRACE,
3737
OPENBRACKET,
38+
OPENANGLEBRACKET,
3839
OPENPAREN,
3940
CLOSEBRACE,
4041
CLOSEBRACKET,
42+
CLOSEANGLEBRACKET,
4143
CLOSEPAREN,
4244
} from './tokens/text';
4345

@@ -55,34 +57,37 @@ let S_START = makeState();
5557

5658
// Intermediate states for URLs. Note that domains that begin with a protocol
5759
// are treated slighly differently from those that don't.
58-
let S_PROTOCOL = makeState(); // e.g., 'http:'
59-
let S_PROTOCOL_SLASH = makeState(); // e.g., '/', 'http:/''
60-
let S_PROTOCOL_SLASH_SLASH = makeState(); // e.g., '//', 'http://'
61-
let S_DOMAIN = makeState(); // parsed string ends with a potential domain name (A)
62-
let S_DOMAIN_DOT = makeState(); // (A) domain followed by DOT
63-
let S_TLD = makeState(URL); // (A) Simplest possible URL with no query string
64-
let S_TLD_COLON = makeState(); // (A) URL followed by colon (potential port number here)
65-
let S_TLD_PORT = makeState(URL); // TLD followed by a port number
66-
let S_URL = makeState(URL); // Long URL with optional port and maybe query string
67-
let S_URL_NON_ACCEPTING = makeState(); // URL followed by some symbols (will not be part of the final URL)
68-
let S_URL_OPENBRACE = makeState(); // URL followed by {
69-
let S_URL_OPENBRACKET = makeState(); // URL followed by [
70-
let S_URL_OPENPAREN = makeState(); // URL followed by (
71-
let S_URL_OPENBRACE_Q = makeState(URL); // URL followed by { and some symbols that the URL can end it
72-
let S_URL_OPENBRACKET_Q = makeState(URL); // URL followed by [ and some symbols that the URL can end it
73-
let S_URL_OPENPAREN_Q = makeState(URL); // URL followed by ( and some symbols that the URL can end it
74-
let S_URL_OPENBRACE_SYMS = makeState(); // S_URL_OPENBRACE_Q followed by some symbols it cannot end it
75-
let S_URL_OPENBRACKET_SYMS = makeState(); // S_URL_OPENBRACKET_Q followed by some symbols it cannot end it
76-
let S_URL_OPENPAREN_SYMS = makeState(); // S_URL_OPENPAREN_Q followed by some symbols it cannot end it
77-
let S_EMAIL_DOMAIN = makeState(); // parsed string starts with local email info + @ with a potential domain name (C)
78-
let S_EMAIL_DOMAIN_DOT = makeState(); // (C) domain followed by DOT
79-
let S_EMAIL = makeState(EMAIL); // (C) Possible email address (could have more tlds)
80-
let S_EMAIL_COLON = makeState(); // (C) URL followed by colon (potential port number here)
81-
let S_EMAIL_PORT = makeState(EMAIL); // (C) Email address with a port
82-
let S_LOCALPART = makeState(); // Local part of the email address
83-
let S_LOCALPART_AT = makeState(); // Local part of the email address plus @
84-
let S_LOCALPART_DOT = makeState(); // Local part of the email address plus '.' (localpart cannot end in .)
85-
let S_NL = makeState(MNL); // single new line
60+
let S_PROTOCOL = makeState(); // e.g., 'http:'
61+
let S_PROTOCOL_SLASH = makeState(); // e.g., '/', 'http:/''
62+
let S_PROTOCOL_SLASH_SLASH = makeState(); // e.g., '//', 'http://'
63+
let S_DOMAIN = makeState(); // parsed string ends with a potential domain name (A)
64+
let S_DOMAIN_DOT = makeState(); // (A) domain followed by DOT
65+
let S_TLD = makeState(URL); // (A) Simplest possible URL with no query string
66+
let S_TLD_COLON = makeState(); // (A) URL followed by colon (potential port number here)
67+
let S_TLD_PORT = makeState(URL); // TLD followed by a port number
68+
let S_URL = makeState(URL); // Long URL with optional port and maybe query string
69+
let S_URL_NON_ACCEPTING = makeState(); // URL followed by some symbols (will not be part of the final URL)
70+
let S_URL_OPENBRACE = makeState(); // URL followed by {
71+
let S_URL_OPENBRACKET = makeState(); // URL followed by [
72+
let S_URL_OPENANGLEBRACKET = makeState(); // URL followed by <
73+
let S_URL_OPENPAREN = makeState(); // URL followed by (
74+
let S_URL_OPENBRACE_Q = makeState(URL); // URL followed by { and some symbols that the URL can end it
75+
let S_URL_OPENBRACKET_Q = makeState(URL); // URL followed by [ and some symbols that the URL can end it
76+
let S_URL_OPENANGLEBRACKET_Q = makeState(URL); // URL followed by < and some symbols that the URL can end it
77+
let S_URL_OPENPAREN_Q = makeState(URL); // URL followed by ( and some symbols that the URL can end it
78+
let S_URL_OPENBRACE_SYMS = makeState(); // S_URL_OPENBRACE_Q followed by some symbols it cannot end it
79+
let S_URL_OPENBRACKET_SYMS = makeState(); // S_URL_OPENBRACKET_Q followed by some symbols it cannot end it
80+
let S_URL_OPENANGLEBRACKET_SYMS = makeState(); // S_URL_OPENANGLEBRACKET_Q followed by some symbols it cannot end it
81+
let S_URL_OPENPAREN_SYMS = makeState(); // S_URL_OPENPAREN_Q followed by some symbols it cannot end it
82+
let S_EMAIL_DOMAIN = makeState(); // parsed string starts with local email info + @ with a potential domain name (C)
83+
let S_EMAIL_DOMAIN_DOT = makeState(); // (C) domain followed by DOT
84+
let S_EMAIL = makeState(EMAIL); // (C) Possible email address (could have more tlds)
85+
let S_EMAIL_COLON = makeState(); // (C) URL followed by colon (potential port number here)
86+
let S_EMAIL_PORT = makeState(EMAIL); // (C) Email address with a port
87+
let S_LOCALPART = makeState(); // Local part of the email address
88+
let S_LOCALPART_AT = makeState(); // Local part of the email address plus @
89+
let S_LOCALPART_DOT = makeState(); // Local part of the email address plus '.' (localpart cannot end in .)
90+
let S_NL = makeState(MNL); // single new line
8691

8792
// Make path from start to protocol (with '//')
8893
S_START
@@ -167,9 +172,11 @@ let qsNonAccepting = [
167172
PUNCTUATION,
168173
CLOSEBRACE,
169174
CLOSEBRACKET,
175+
CLOSEANGLEBRACKET,
170176
CLOSEPAREN,
171177
OPENBRACE,
172178
OPENBRACKET,
179+
OPENANGLEBRACKET,
173180
OPENPAREN
174181
];
175182

@@ -180,48 +187,59 @@ let qsNonAccepting = [
180187
S_URL
181188
.on(OPENBRACE, S_URL_OPENBRACE)
182189
.on(OPENBRACKET, S_URL_OPENBRACKET)
190+
.on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET)
183191
.on(OPENPAREN, S_URL_OPENPAREN);
184192

185193
// URL with extra symbols at the end, followed by an opening bracket
186194
S_URL_NON_ACCEPTING
187195
.on(OPENBRACE, S_URL_OPENBRACE)
188196
.on(OPENBRACKET, S_URL_OPENBRACKET)
197+
.on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET)
189198
.on(OPENPAREN, S_URL_OPENPAREN);
190199

191200
// Closing bracket component. This character WILL be included in the URL
192201
S_URL_OPENBRACE.on(CLOSEBRACE, S_URL);
193202
S_URL_OPENBRACKET.on(CLOSEBRACKET, S_URL);
203+
S_URL_OPENANGLEBRACKET.on(CLOSEANGLEBRACKET, S_URL);
194204
S_URL_OPENPAREN.on(CLOSEPAREN, S_URL);
195205
S_URL_OPENBRACE_Q.on(CLOSEBRACE, S_URL);
196206
S_URL_OPENBRACKET_Q.on(CLOSEBRACKET, S_URL);
207+
S_URL_OPENANGLEBRACKET_Q.on(CLOSEANGLEBRACKET, S_URL);
197208
S_URL_OPENPAREN_Q.on(CLOSEPAREN, S_URL);
198209
S_URL_OPENBRACE_SYMS.on(CLOSEBRACE, S_URL);
199210
S_URL_OPENBRACKET_SYMS.on(CLOSEBRACKET, S_URL);
211+
S_URL_OPENANGLEBRACKET_SYMS.on(CLOSEANGLEBRACKET, S_URL);
200212
S_URL_OPENPAREN_SYMS.on(CLOSEPAREN, S_URL);
201213

202214
// URL that beings with an opening bracket, followed by a symbols.
203215
// Note that the final state can still be `S_URL_OPENBRACE_Q` (if the URL only
204216
// has a single opening bracket for some reason).
205217
S_URL_OPENBRACE.on(qsAccepting, S_URL_OPENBRACE_Q);
206218
S_URL_OPENBRACKET.on(qsAccepting, S_URL_OPENBRACKET_Q);
219+
S_URL_OPENANGLEBRACKET.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
207220
S_URL_OPENPAREN.on(qsAccepting, S_URL_OPENPAREN_Q);
208221
S_URL_OPENBRACE.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
209222
S_URL_OPENBRACKET.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
223+
S_URL_OPENANGLEBRACKET.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
210224
S_URL_OPENPAREN.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
211225

212226
// URL that begins with an opening bracket, followed by some symbols
213227
S_URL_OPENBRACE_Q.on(qsAccepting, S_URL_OPENBRACE_Q);
214228
S_URL_OPENBRACKET_Q.on(qsAccepting, S_URL_OPENBRACKET_Q);
229+
S_URL_OPENANGLEBRACKET_Q.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
215230
S_URL_OPENPAREN_Q.on(qsAccepting, S_URL_OPENPAREN_Q);
216231
S_URL_OPENBRACE_Q.on(qsNonAccepting, S_URL_OPENBRACE_Q);
217232
S_URL_OPENBRACKET_Q.on(qsNonAccepting, S_URL_OPENBRACKET_Q);
233+
S_URL_OPENANGLEBRACKET_Q.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_Q);
218234
S_URL_OPENPAREN_Q.on(qsNonAccepting, S_URL_OPENPAREN_Q);
219235

220236
S_URL_OPENBRACE_SYMS.on(qsAccepting, S_URL_OPENBRACE_Q);
221237
S_URL_OPENBRACKET_SYMS.on(qsAccepting, S_URL_OPENBRACKET_Q);
238+
S_URL_OPENANGLEBRACKET_SYMS.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
222239
S_URL_OPENPAREN_SYMS.on(qsAccepting, S_URL_OPENPAREN_Q);
223240
S_URL_OPENBRACE_SYMS.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
224241
S_URL_OPENBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
242+
S_URL_OPENANGLEBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
225243
S_URL_OPENPAREN_SYMS.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
226244

227245
// Account for the query string

src/linkify/core/scanner.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import {
2626
COLON,
2727
OPENBRACE,
2828
OPENBRACKET,
29+
OPENANGLEBRACKET,
2930
OPENPAREN,
3031
CLOSEBRACE,
3132
CLOSEBRACKET,
33+
CLOSEANGLEBRACKET,
3234
CLOSEPAREN,
3335
PUNCTUATION,
3436
NL,
@@ -63,9 +65,11 @@ S_START
6365
.on(':', makeState(COLON))
6466
.on('{', makeState(OPENBRACE))
6567
.on('[', makeState(OPENBRACKET))
68+
.on('<', makeState(OPENANGLEBRACKET))
6669
.on('(', makeState(OPENPAREN))
6770
.on('}', makeState(CLOSEBRACE))
6871
.on(']', makeState(CLOSEBRACKET))
72+
.on('>', makeState(CLOSEANGLEBRACKET))
6973
.on(')', makeState(CLOSEPAREN))
7074
.on([',', ';', '!', '"', '\''], makeState(PUNCTUATION));
7175

src/linkify/core/tokens/text.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,11 @@ const WS = inheritsToken();
153153

154154
const OPENBRACE = inheritsToken('{');
155155
const OPENBRACKET = inheritsToken('[');
156+
const OPENANGLEBRACKET = inheritsToken('<');
156157
const OPENPAREN = inheritsToken('(');
157158
const CLOSEBRACE = inheritsToken('}');
158159
const CLOSEBRACKET = inheritsToken(']');
160+
const CLOSEANGLEBRACKET = inheritsToken('>');
159161
const CLOSEPAREN = inheritsToken(')');
160162

161163
export {
@@ -179,8 +181,10 @@ export {
179181
WS,
180182
OPENBRACE,
181183
OPENBRACKET,
184+
OPENANGLEBRACKET,
182185
OPENPAREN,
183186
CLOSEBRACE,
184187
CLOSEBRACKET,
188+
CLOSEANGLEBRACKET,
185189
CLOSEPAREN
186190
};

test/spec/linkify/core/parser-test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ var tests = [
127127
'A really funky one (example.com/?id=asd2{hellow}and%20it%20continues(23&((@)) and it ends',
128128
[TEXT, URL, TEXT],
129129
['A really funky one (', 'example.com/?id=asd2{hellow}and%20it%20continues(23&((@)', ') and it ends']
130+
], [
131+
'URL enclosed in angle brackets: <http://example.com/exemplary> should not include trailing bracket',
132+
[TEXT, URL, TEXT],
133+
['URL enclosed in angle brackets: <', 'http://example.com/exemplary', '> should not include trailing bracket']
134+
], [
135+
'URL with angle brackets in it: http://example.com/exemplary_<remix> should be included',
136+
[TEXT, URL, TEXT],
137+
['URL with angle brackets in it: ', 'http://example.com/exemplary_<remix>', ' should be included']
130138
], [
131139
'Force http:/ and http:// are not but http://a and http://b.local?qeasd3qas=23 are all links',
132140
[TEXT, URL, TEXT, URL, TEXT],

test/spec/linkify/core/scanner-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ const TLD = TEXTTOKENS.TLD;
1919
const WS = TEXTTOKENS.WS;
2020
const OPENBRACE = TEXTTOKENS.OPENBRACE;
2121
const OPENBRACKET = TEXTTOKENS.OPENBRACKET;
22+
const OPENANGLEBRACKET = TEXTTOKENS.OPENANGLEBRACKET;
2223
const OPENPAREN = TEXTTOKENS.OPENPAREN;
2324
const CLOSEBRACE = TEXTTOKENS.CLOSEBRACE;
2425
const CLOSEBRACKET = TEXTTOKENS.CLOSEBRACKET;
26+
const CLOSEANGLEBRACKET = TEXTTOKENS.CLOSEANGLEBRACKET;
2527
const CLOSEPAREN = TEXTTOKENS.CLOSEPAREN;
2628

2729
// The elements are
@@ -39,7 +41,7 @@ const tests = [
3941
['#', [POUND], ['#']],
4042
['/', [SLASH], ['/']],
4143
['&', [SYM], ['&']],
42-
['&?<>(', [SYM, QUERY, SYM, SYM, OPENPAREN], ['&', '?', '<', '>', '(']],
44+
['&?<>(', [SYM, QUERY, OPENANGLEBRACKET, CLOSEANGLEBRACKET, OPENPAREN], ['&', '?', '<', '>', '(']],
4345
['([{}])', [OPENPAREN, OPENBRACKET, OPENBRACE, CLOSEBRACE, CLOSEBRACKET, CLOSEPAREN], ['(', '[', '{', '}', ']', ')']],
4446
['!,;\'', [PUNCTUATION, PUNCTUATION, PUNCTUATION, PUNCTUATION], ['!', ',', ';', '\'']],
4547
['hello', [DOMAIN], ['hello']],

0 commit comments

Comments
 (0)