@wordpress/shortcode 4.37.1-next.ba3aee3a2.0 → 4.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,18 +3,34 @@
3
3
  */
4
4
  import memize from 'memize';
5
5
 
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import type {
10
+ ShortcodeAttrs,
11
+ ShortcodeMatch,
12
+ ShortcodeOptions,
13
+ Match,
14
+ ReplaceCallback,
15
+ ShortcodeInstance,
16
+ } from './types';
17
+
6
18
  export * from './types';
7
19
 
8
20
  /**
9
21
  * Find the next matching shortcode.
10
22
  *
11
- * @param {string} tag Shortcode tag.
12
- * @param {string} text Text to search.
13
- * @param {number} index Index to start search from.
23
+ * @param tag Shortcode tag.
24
+ * @param text Text to search.
25
+ * @param index Index to start search from.
14
26
  *
15
- * @return {import('./types').ShortcodeMatch | undefined} Matched information.
27
+ * @return Matched information.
16
28
  */
17
- export function next( tag, text, index = 0 ) {
29
+ export function next(
30
+ tag: string,
31
+ text: string,
32
+ index: number = 0
33
+ ): ShortcodeMatch | undefined {
18
34
  const re = regexp( tag );
19
35
 
20
36
  re.lastIndex = index;
@@ -30,7 +46,7 @@ export function next( tag, text, index = 0 ) {
30
46
  return next( tag, text, re.lastIndex );
31
47
  }
32
48
 
33
- const result = {
49
+ const result: ShortcodeMatch = {
34
50
  index: match.index,
35
51
  content: match[ 0 ],
36
52
  shortcode: fromMatch( match ),
@@ -54,17 +70,26 @@ export function next( tag, text, index = 0 ) {
54
70
  /**
55
71
  * Replace matching shortcodes in a block of text.
56
72
  *
57
- * @param {string} tag Shortcode tag.
58
- * @param {string} text Text to search.
59
- * @param {import('./types').ReplaceCallback} callback Function to process the match and return
60
- * replacement string.
73
+ * @param tag Shortcode tag.
74
+ * @param text Text to search.
75
+ * @param callback Function to process the match and return
76
+ * replacement string.
61
77
  *
62
- * @return {string} Text with shortcodes replaced.
78
+ * @return Text with shortcodes replaced.
63
79
  */
64
- export function replace( tag, text, callback ) {
80
+ export function replace(
81
+ tag: string,
82
+ text: string,
83
+ callback: ReplaceCallback
84
+ ) {
65
85
  return text.replace(
66
86
  regexp( tag ),
67
- function ( match, left, $3, attrs, slash, content, closing, right ) {
87
+ // Let us use spread syntax to capture the arguments object.
88
+ ( ...args: string[] ): string => {
89
+ const match = args[ 0 ];
90
+ const left = args[ 1 ];
91
+ const right = args[ 7 ];
92
+
68
93
  // If both extra brackets exist, the shortcode has been properly
69
94
  // escaped.
70
95
  if ( left === '[' && right === ']' ) {
@@ -72,7 +97,7 @@ export function replace( tag, text, callback ) {
72
97
  }
73
98
 
74
99
  // Create the match object and pass it through the callback.
75
- const result = callback( fromMatch( arguments ) );
100
+ const result = callback( fromMatch( args ) );
76
101
 
77
102
  // Make sure to return any of the extra brackets if they weren't used to
78
103
  // escape the shortcode.
@@ -90,12 +115,12 @@ export function replace( tag, text, callback ) {
90
115
  * `tag` string, a string or object of `attrs`, a boolean indicating whether to
91
116
  * format the shortcode using a `single` tag, and a `content` string.
92
117
  *
93
- * @param {Object} options
118
+ * @param options Shortcode options.
94
119
  *
95
- * @return {string} String representation of the shortcode.
120
+ * @return String representation of the shortcode.
96
121
  */
97
- export function string( options ) {
98
- return new shortcode( options ).string();
122
+ export function string( options: ShortcodeOptions ): string {
123
+ return new Shortcode( options ).string();
99
124
  }
100
125
 
101
126
  /**
@@ -114,11 +139,11 @@ export function string( options ) {
114
139
  * 6. The closing tag.
115
140
  * 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
116
141
  *
117
- * @param {string} tag Shortcode tag.
142
+ * @param tag Shortcode tag.
118
143
  *
119
- * @return {RegExp} Shortcode RegExp.
144
+ * @return Shortcode RegExp.
120
145
  */
121
- export function regexp( tag ) {
146
+ export function regexp( tag: string ): RegExp {
122
147
  return new RegExp(
123
148
  '\\[(\\[?)(' +
124
149
  tag +
@@ -142,11 +167,11 @@ export function regexp( tag ) {
142
167
  *
143
168
  * @param {string} text Serialised shortcode attributes.
144
169
  *
145
- * @return {import('./types').ShortcodeAttrs} Parsed shortcode attributes.
170
+ * @return {ShortcodeAttrs} Parsed shortcode attributes.
146
171
  */
147
- export const attrs = memize( ( text ) => {
148
- const named = {};
149
- const numeric = [];
172
+ export const attrs = memize( ( text: string ): ShortcodeAttrs => {
173
+ const named: Record< string, string | undefined > = {};
174
+ const numeric: string[] = [];
150
175
 
151
176
  // This regular expression is reused from `shortcode_parse_atts()` in
152
177
  // `wp-includes/shortcodes.php`.
@@ -197,12 +222,12 @@ export const attrs = memize( ( text ) => {
197
222
  * by `regexp()`. `match` can also be set to the `arguments` from a callback
198
223
  * passed to `regexp.replace()`.
199
224
  *
200
- * @param {import('./types').Match} match Match array.
225
+ * @param match Match array.
201
226
  *
202
- * @return {InstanceType<import('./types').shortcode>} Shortcode instance.
227
+ * @return Shortcode instance.
203
228
  */
204
- export function fromMatch( match ) {
205
- let type;
229
+ export function fromMatch( match: Match ): ShortcodeInstance {
230
+ let type: 'self-closing' | 'closed' | 'single';
206
231
 
207
232
  if ( match[ 4 ] ) {
208
233
  type = 'self-closing';
@@ -212,7 +237,7 @@ export function fromMatch( match ) {
212
237
  type = 'single';
213
238
  }
214
239
 
215
- return new shortcode( {
240
+ return new Shortcode( {
216
241
  tag: match[ 2 ],
217
242
  attrs: match[ 3 ],
218
243
  type,
@@ -227,13 +252,27 @@ export function fromMatch( match ) {
227
252
  * containing a `tag` string, a string or object of `attrs`, a string indicating
228
253
  * the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a
229
254
  * `content` string.
230
- *
231
- * @type {import('./types').shortcode} Shortcode instance.
232
255
  */
233
- const shortcode = Object.assign(
234
- function ( options ) {
235
- const { tag, attrs: attributes, type, content } = options || {};
236
- Object.assign( this, { tag, type, content } );
256
+ class Shortcode implements ShortcodeInstance {
257
+ // Instance properties
258
+ tag: string;
259
+ type?: 'self-closing' | 'closed' | 'single';
260
+ content?: string;
261
+ attrs: ShortcodeAttrs;
262
+
263
+ // Static methods
264
+ static next = next;
265
+ static replace = replace;
266
+ static string = string;
267
+ static regexp = regexp;
268
+ static attrs = attrs;
269
+ static fromMatch = fromMatch;
270
+
271
+ constructor( options: ShortcodeOptions ) {
272
+ const { tag, attrs: attributes, type, content } = options;
273
+ this.tag = tag;
274
+ this.type = type;
275
+ this.content = content;
237
276
 
238
277
  // Ensure we have a correctly formatted `attrs` object.
239
278
  this.attrs = {
@@ -245,50 +284,46 @@ const shortcode = Object.assign(
245
284
  return;
246
285
  }
247
286
 
248
- const attributeTypes = [ 'named', 'numeric' ];
249
-
250
287
  // Parse a string of attributes.
251
288
  if ( typeof attributes === 'string' ) {
252
289
  this.attrs = attrs( attributes );
253
290
  // Identify a correctly formatted `attrs` object.
254
291
  } else if (
255
- attributes.length === attributeTypes.length &&
256
- attributeTypes.every( ( t, key ) => t === attributes[ key ] )
292
+ 'named' in attributes &&
293
+ 'numeric' in attributes &&
294
+ attributes.named !== undefined &&
295
+ attributes.numeric !== undefined
257
296
  ) {
258
- this.attrs = attributes;
259
- // Handle a flat object of attributes.
297
+ this.attrs = attributes as ShortcodeAttrs;
298
+ // Handle a flat object of attributes (e.g., { foo: 'bar', baz: 'qux' }).
260
299
  } else {
261
300
  Object.entries( attributes ).forEach( ( [ key, value ] ) => {
262
- this.set( key, value );
301
+ if (
302
+ typeof value === 'string' ||
303
+ typeof value === 'undefined'
304
+ ) {
305
+ this.set( key, value );
306
+ }
263
307
  } );
264
308
  }
265
- },
266
- {
267
- next,
268
- replace,
269
- string,
270
- regexp,
271
- attrs,
272
- fromMatch,
273
309
  }
274
- );
275
310
 
276
- Object.assign( shortcode.prototype, {
277
311
  /**
278
312
  * Get a shortcode attribute.
279
313
  *
280
314
  * Automatically detects whether `attr` is named or numeric and routes it
281
315
  * accordingly.
282
316
  *
283
- * @param {(number|string)} attr Attribute key.
317
+ * @param attr Attribute key.
284
318
  *
285
- * @return {string} Attribute value.
319
+ * @return Attribute value.
286
320
  */
287
- get( attr ) {
288
- return this.attrs[ typeof attr === 'number' ? 'numeric' : 'named' ][
289
- attr
290
- ];
291
- },
321
+ get( attr: string | number ): string | undefined {
322
+ if ( typeof attr === 'number' ) {
323
+ return this.attrs.numeric[ attr ];
324
+ }
325
+ return this.attrs.named[ attr ];
326
+ }
292
327
 
293
328
  /**
294
329
  * Set a shortcode attribute.
@@ -296,23 +331,26 @@ Object.assign( shortcode.prototype, {
296
331
  * Automatically detects whether `attr` is named or numeric and routes it
297
332
  * accordingly.
298
333
  *
299
- * @param {(number|string)} attr Attribute key.
300
- * @param {string} value Attribute value.
334
+ * @param attr Attribute key.
335
+ * @param value Attribute value.
301
336
  *
302
- * @return {InstanceType< import('./types').shortcode >} Shortcode instance.
337
+ * @return Shortcode instance.
303
338
  */
304
- set( attr, value ) {
305
- this.attrs[ typeof attr === 'number' ? 'numeric' : 'named' ][ attr ] =
306
- value;
339
+ set( attr: string | number, value: string ): this {
340
+ if ( typeof attr === 'number' ) {
341
+ this.attrs.numeric[ attr ] = value;
342
+ } else {
343
+ this.attrs.named[ attr ] = value;
344
+ }
307
345
  return this;
308
- },
346
+ }
309
347
 
310
348
  /**
311
349
  * Transform the shortcode into a string.
312
350
  *
313
- * @return {string} String representation of the shortcode.
351
+ * @return String representation of the shortcode.
314
352
  */
315
- string() {
353
+ string(): string {
316
354
  let text = '[' + this.tag;
317
355
 
318
356
  this.attrs.numeric.forEach( ( value ) => {
@@ -344,7 +382,7 @@ Object.assign( shortcode.prototype, {
344
382
 
345
383
  // Add the closing tag.
346
384
  return text + '[/' + this.tag + ']';
347
- },
348
- } );
385
+ }
386
+ }
349
387
 
350
- export default shortcode;
388
+ export default Shortcode;
@@ -7,7 +7,7 @@ describe( 'shortcode', () => {
7
7
  describe( 'next', () => {
8
8
  it( 'should find the shortcode', () => {
9
9
  const result = next( 'foo', 'this has the [foo] shortcode' );
10
- expect( result.index ).toBe( 13 );
10
+ expect( result?.index ).toBe( 13 );
11
11
  } );
12
12
 
13
13
  it( 'should find the shortcode with attributes', () => {
@@ -15,7 +15,7 @@ describe( 'shortcode', () => {
15
15
  'foo',
16
16
  'this has the [foo param="foo"] shortcode'
17
17
  );
18
- expect( result.index ).toBe( 13 );
18
+ expect( result?.index ).toBe( 13 );
19
19
  } );
20
20
 
21
21
  it( 'should not find shortcodes that are not there', () => {
@@ -33,10 +33,10 @@ describe( 'shortcode', () => {
33
33
 
34
34
  it( 'should find the shortcode when told to start looking beyond the start of the string', () => {
35
35
  const result1 = next( 'foo', 'this has the [foo] shortcode', 12 );
36
- expect( result1.index ).toBe( 13 );
36
+ expect( result1?.index ).toBe( 13 );
37
37
 
38
38
  const result2 = next( 'foo', 'this has the [foo] shortcode', 13 );
39
- expect( result2.index ).toBe( 13 );
39
+ expect( result2?.index ).toBe( 13 );
40
40
 
41
41
  const result3 = next( 'foo', 'this has the [foo] shortcode', 14 );
42
42
  expect( result3 ).toBe( undefined );
@@ -48,7 +48,7 @@ describe( 'shortcode', () => {
48
48
  'this has the [foo] shortcode [foo] twice',
49
49
  14
50
50
  );
51
- expect( result.index ).toBe( 29 );
51
+ expect( result?.index ).toBe( 29 );
52
52
  } );
53
53
 
54
54
  it( 'should not find escaped shortcodes', () => {
@@ -66,18 +66,18 @@ describe( 'shortcode', () => {
66
66
 
67
67
  it( 'should find shortcodes that are incorrectly escaped by newlines', () => {
68
68
  const result1 = next( 'foo', 'this has the [\n[foo]] shortcode' );
69
- expect( result1.index ).toBe( 15 );
69
+ expect( result1?.index ).toBe( 15 );
70
70
 
71
71
  const result2 = next( 'foo', 'this has the [[foo]\n] shortcode' );
72
- expect( result2.index ).toBe( 14 );
72
+ expect( result2?.index ).toBe( 14 );
73
73
  } );
74
74
 
75
75
  it( 'should still work when there are not equal amounts of square brackets', () => {
76
76
  const result1 = next( 'foo', 'this has the [[foo] shortcode' );
77
- expect( result1.index ).toBe( 14 );
77
+ expect( result1?.index ).toBe( 14 );
78
78
 
79
79
  const result2 = next( 'foo', 'this has the [foo]] shortcode' );
80
- expect( result2.index ).toBe( 13 );
80
+ expect( result2?.index ).toBe( 13 );
81
81
  } );
82
82
 
83
83
  it( 'should find the second instances of the shortcode when the first one is escaped', () => {
@@ -85,7 +85,7 @@ describe( 'shortcode', () => {
85
85
  'foo',
86
86
  'this has the [[foo]] shortcode [foo] twice'
87
87
  );
88
- expect( result.index ).toBe( 31 );
88
+ expect( result?.index ).toBe( 31 );
89
89
  } );
90
90
 
91
91
  it( 'should not find shortcodes that are not full matches', () => {
@@ -221,13 +221,13 @@ describe( 'shortcode', () => {
221
221
  } );
222
222
 
223
223
  it( 'should replace shortcode with a number', () => {
224
- const result1 = replace( 'foo', 'hello [foo] world', () => 3 );
224
+ const result1 = replace( 'foo', 'hello [foo] world', () => '3' );
225
225
  expect( result1 ).toBe( 'hello 3 world' );
226
226
 
227
227
  const result2 = replace(
228
228
  'foo',
229
229
  'hello [foo bar=bar baz="baz" qux]delete me[/foo] world',
230
- () => 4
230
+ () => '4'
231
231
  );
232
232
  expect( result2 ).toBe( 'hello 4 world' );
233
233
  } );
package/src/types.ts CHANGED
@@ -13,6 +13,34 @@ export type ShortcodeAttrs = {
13
13
  numeric: string[];
14
14
  };
15
15
 
16
+ /**
17
+ * Shortcode object.
18
+ */
19
+ export interface Shortcode {
20
+ /**
21
+ * Shortcode tag.
22
+ */
23
+ tag: string;
24
+
25
+ /**
26
+ * Shortcode attributes.
27
+ */
28
+ attrs: ShortcodeAttrs;
29
+
30
+ /**
31
+ * Shortcode content.
32
+ */
33
+ content?: string;
34
+
35
+ /**
36
+ * Shortcode type: `self-closing`, `closed`, or `single`.
37
+ */
38
+ type?: 'self-closing' | 'closed' | 'single';
39
+ }
40
+
41
+ /**
42
+ * Shortcode match result.
43
+ */
16
44
  export type ShortcodeMatch = {
17
45
  /**
18
46
  * Index the shortcode is found at.
@@ -31,7 +59,7 @@ export type ShortcodeMatch = {
31
59
  };
32
60
 
33
61
  /**
34
- * Shortcode options.
62
+ * Shortcode options for creating a new shortcode.
35
63
  */
36
64
  export interface ShortcodeOptions {
37
65
  /**
@@ -56,155 +84,51 @@ export interface ShortcodeOptions {
56
84
  }
57
85
 
58
86
  /**
59
- * Shortcode object.
87
+ * Match array from regexp.exec() or arguments from replace callback.
60
88
  */
61
- export interface Shortcode extends ShortcodeOptions {
62
- /**
63
- * Shortcode attributes.
64
- */
65
- attrs: ShortcodeAttrs;
66
- }
67
-
68
89
  export type Match =
69
90
  | NonNullable< ReturnType< RegExp[ 'exec' ] > >
70
- | Array< string >;
91
+ | IArguments
92
+ | ArrayLike< string >;
71
93
 
94
+ /**
95
+ * Callback function for replace operations.
96
+ */
72
97
  export type ReplaceCallback = ( shortcode: Shortcode ) => string;
73
98
 
74
99
  /**
75
- * WordPress Shortcode instance.
100
+ * Shortcode instance returned by the constructor.
76
101
  */
77
- export interface shortcode {
78
- new ( options: Partial< ShortcodeOptions > ): Shortcode & {
79
- /**
80
- * Transform the shortcode into a string.
81
- *
82
- * @return {string} String representation of the shortcode.
83
- */
84
- string: () => string;
85
-
86
- /**
87
- * Get a shortcode attribute.
88
- *
89
- * Automatically detects whether `attr` is named or numeric and routes it
90
- * accordingly.
91
- *
92
- * @param {(number|string)} attr Attribute key.
93
- *
94
- * @return {string} Attribute value.
95
- */
96
- get: ( attr: string | number ) => string | undefined;
97
-
98
- /**
99
- * Set a shortcode attribute.
100
- *
101
- * Automatically detects whether `attr` is named or numeric and routes it
102
- * accordingly.
103
- *
104
- * @param {(number|string)} attr Attribute key.
105
- * @param {string} value Attribute value.
106
- *
107
- * @return {InstanceType< shortcode >} Shortcode instance.
108
- */
109
- set: (
110
- attr: string | number,
111
- value: string
112
- ) => InstanceType< shortcode >;
113
- };
114
-
115
- /**
116
- * Parse shortcode attributes.
117
- *
118
- * Shortcodes accept many types of attributes. These can chiefly be divided into
119
- * named and numeric attributes:
120
- *
121
- * Named attributes are assigned on a key/value basis, while numeric attributes
122
- * are treated as an array.
123
- *
124
- * Named attributes can be formatted as either `name="value"`, `name='value'`,
125
- * or `name=value`. Numeric attributes can be formatted as `"value"` or just
126
- * `value`.
127
- *
128
- * @param text Serialised shortcode attributes.
129
- *
130
- * @return Parsed shortcode attributes.
131
- */
132
- attrs: ( text: string ) => ShortcodeAttrs;
133
-
134
- /**
135
- * Generate a Shortcode Object from a RegExp match.
136
- *
137
- * Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated
138
- * by `regexp()`. `match` can also be set to the `arguments` from a callback
139
- * passed to `regexp.replace()`.
140
- *
141
- * @param match Match array.
142
- *
143
- * @return Shortcode instance.
144
- */
145
- fromMatch: ( match: Match ) => InstanceType< shortcode >;
146
-
102
+ export interface ShortcodeInstance extends Shortcode {
147
103
  /**
148
- * Find the next matching shortcode.
149
- *
150
- * @param tag Shortcode tag.
151
- * @param text Text to search.
152
- * @param index Index to start search from.
104
+ * Transform the shortcode into a string.
153
105
  *
154
- * @return Matched information.
106
+ * @return String representation of the shortcode.
155
107
  */
156
- next: (
157
- tag: string,
158
- text: string,
159
- index?: number
160
- ) => ShortcodeMatch | undefined;
108
+ string: () => string;
161
109
 
162
110
  /**
163
- * Generate a RegExp to identify a shortcode.
164
- *
165
- * The base regex is functionally equivalent to the one found in
166
- * `get_shortcode_regex()` in `wp-includes/shortcodes.php`.
111
+ * Get a shortcode attribute.
167
112
  *
168
- * Capture groups:
113
+ * Automatically detects whether `attr` is named or numeric and routes it
114
+ * accordingly.
169
115
  *
170
- * 1. An extra `[` to allow for escaping shortcodes with double `[[]]`
171
- * 2. The shortcode name
172
- * 3. The shortcode argument list
173
- * 4. The self closing `/`
174
- * 5. The content of a shortcode when it wraps some content.
175
- * 6. The closing tag.
176
- * 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
116
+ * @param attr Attribute key.
177
117
  *
178
- * @param tag Shortcode tag.
179
- *
180
- * @return Shortcode RegExp.
118
+ * @return Attribute value.
181
119
  */
182
- regexp: ( tag: string ) => RegExp;
120
+ get: ( attr: string | number ) => string | undefined;
183
121
 
184
122
  /**
185
- * Replace matching shortcodes in a block of text.
186
- *
187
- * @param tag Shortcode tag.
188
- * @param text Text to search.
189
- * @param callback Function to process the match and return
190
- * replacement string.
191
- *
192
- * @return Text with shortcodes replaced.
193
- */
194
- replace: ( tag: string, text: string, callback: ReplaceCallback ) => string;
195
-
196
- /**
197
- * Generate a string from shortcode parameters.
198
- *
199
- * Creates a shortcode instance and returns a string.
123
+ * Set a shortcode attribute.
200
124
  *
201
- * Accepts the same `options` as the `shortcode()` constructor, containing a
202
- * `tag` string, a string or object of `attrs`, a boolean indicating whether to
203
- * format the shortcode using a `single` tag, and a `content` string.
125
+ * Automatically detects whether `attr` is named or numeric and routes it
126
+ * accordingly.
204
127
  *
205
- * @param options
128
+ * @param attr Attribute key.
129
+ * @param value Attribute value.
206
130
  *
207
- * @return String representation of the shortcode.
131
+ * @return Shortcode instance.
208
132
  */
209
- string: ( options: ShortcodeOptions ) => string;
133
+ set: ( attr: string | number, value: string ) => ShortcodeInstance;
210
134
  }