shaka-player 4.1.0 → 4.1.3

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.
Files changed (162) hide show
  1. package/.github/workflows/build-and-test.yaml +67 -15
  2. package/.github/workflows/compute-incremental-coverage.py +251 -0
  3. package/.github/workflows/report-incremental-coverage.yaml +39 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +44 -1
  5. package/AUTHORS +2 -0
  6. package/CHANGELOG.md +42 -0
  7. package/CONTRIBUTORS +4 -0
  8. package/README.md +20 -6
  9. package/demo/common/assets.js +1 -1
  10. package/demo/index.html +1 -3
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +1 -1
  13. package/dist/demo.compiled.debug.js +3 -3
  14. package/dist/demo.compiled.debug.map +2 -2
  15. package/dist/demo.compiled.js +3 -3
  16. package/dist/demo.compiled.map +2 -2
  17. package/dist/deps.js +3 -3
  18. package/dist/locales.js +29 -29
  19. package/dist/receiver.compiled.debug.js +1 -1
  20. package/dist/receiver.compiled.debug.map +1 -1
  21. package/dist/receiver.compiled.js +1 -1
  22. package/dist/receiver.compiled.map +1 -1
  23. package/dist/shaka-player.compiled.d.ts +12 -1
  24. package/dist/shaka-player.compiled.debug.d.ts +12 -1
  25. package/dist/shaka-player.compiled.debug.externs.js +52 -40
  26. package/dist/shaka-player.compiled.debug.js +270 -257
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +52 -40
  29. package/dist/shaka-player.compiled.js +754 -746
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +12 -1
  32. package/dist/shaka-player.ui.debug.d.ts +12 -1
  33. package/dist/shaka-player.ui.debug.externs.js +52 -40
  34. package/dist/shaka-player.ui.debug.js +296 -283
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +52 -40
  37. package/dist/shaka-player.ui.js +874 -867
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/ui.md +6 -6
  40. package/docs/tutorials/upgrade.md +1 -0
  41. package/docs/tutorials/welcome.md +6 -3
  42. package/externs/shaka/player.js +4 -2
  43. package/karma.conf.js +3 -11
  44. package/lib/ads/client_side_ad_manager.js +1 -1
  45. package/lib/dash/dash_parser.js +5 -0
  46. package/lib/hls/hls_parser.js +183 -70
  47. package/lib/media/drm_engine.js +1 -1
  48. package/lib/media/media_source_engine.js +57 -24
  49. package/lib/media/presentation_timeline.js +24 -1
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +4 -4
  53. package/lib/media/time_ranges_utils.js +46 -0
  54. package/lib/player.js +1 -6
  55. package/lib/polyfill/media_capabilities.js +9 -0
  56. package/lib/polyfill/videoplaybackquality.js +3 -1
  57. package/lib/text/cue.js +0 -5
  58. package/lib/text/simple_text_displayer.js +21 -9
  59. package/lib/text/ui_text_displayer.js +83 -19
  60. package/lib/util/buffer_utils.js +3 -0
  61. package/lib/util/object_utils.js +1 -0
  62. package/lib/util/platform.js +19 -1
  63. package/lib/util/stream_utils.js +24 -3
  64. package/lib/util/string_utils.js +102 -13
  65. package/maintained-branches.md +15 -0
  66. package/package.json +3 -5
  67. package/test/hls/hls_parser_unit.js +102 -8
  68. package/test/media/drm_engine_integration.js +24 -8
  69. package/test/media/media_source_engine_integration.js +24 -10
  70. package/test/media/media_source_engine_unit.js +53 -38
  71. package/test/media/streaming_engine_unit.js +28 -27
  72. package/test/test/util/fake_media_source_engine.js +10 -9
  73. package/test/text/simple_text_displayer_unit.js +27 -0
  74. package/test/text/ui_text_displayer_unit.js +65 -0
  75. package/test/util/string_utils_unit.js +46 -8
  76. package/ui/text_selection.js +2 -2
  77. package/app-engine/README.md +0 -9
  78. package/app-engine/demo-version-index/README.md +0 -14
  79. package/app-engine/demo-version-index/app.yaml +0 -28
  80. package/app-engine/demo-version-index/generate.py +0 -164
  81. package/app-engine/demo-version-index/main.py +0 -11
  82. package/app-engine/demo-version-index/requirements.txt +0 -5
  83. package/app-engine/demo-version-index/templates/index.html +0 -114
  84. package/app-engine/shaka-player-demo/README.md +0 -18
  85. package/app-engine/shaka-player-demo/app.yaml +0 -61
  86. package/app-engine/shaka-player-demo/main.py +0 -95
  87. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  88. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  89. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  90. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  91. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  92. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  93. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  94. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  95. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  96. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  97. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  98. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  99. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  100. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  101. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  102. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  103. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  104. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  105. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  106. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  107. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  108. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  109. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  110. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  111. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  112. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  113. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  114. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  115. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  116. package/app-engine/shaka-player-demo/posters.py +0 -110
  117. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  118. package/app-engine/shaka-player-demo/time.txt +0 -1
  119. package/build/README.md +0 -127
  120. package/build/__init__.py +0 -0
  121. package/build/all.py +0 -150
  122. package/build/apps.py +0 -186
  123. package/build/build.py +0 -392
  124. package/build/check.py +0 -354
  125. package/build/checkversion.py +0 -84
  126. package/build/compiler.py +0 -513
  127. package/build/conformance.textproto +0 -335
  128. package/build/docker/Dockerfile +0 -20
  129. package/build/docker/docker-compose.yml +0 -10
  130. package/build/docs.py +0 -54
  131. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  132. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  133. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  134. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  135. package/build/gendeps.py +0 -71
  136. package/build/generateExterns.js +0 -867
  137. package/build/generateLocalizations.py +0 -262
  138. package/build/generateTsDefs.py +0 -137
  139. package/build/install-linux-prereqs.sh +0 -136
  140. package/build/license-header +0 -5
  141. package/build/misspellings.txt +0 -39
  142. package/build/pixelsChanged.js +0 -35
  143. package/build/shaka-lab.yaml +0 -161
  144. package/build/shakaBuildHelpers.py +0 -356
  145. package/build/stats.py +0 -816
  146. package/build/subprocessWindowsPatch.py +0 -103
  147. package/build/test.py +0 -541
  148. package/build/types/ads +0 -8
  149. package/build/types/cast +0 -6
  150. package/build/types/cea +0 -13
  151. package/build/types/complete +0 -9
  152. package/build/types/core +0 -108
  153. package/build/types/dash +0 -8
  154. package/build/types/hls +0 -9
  155. package/build/types/manifests +0 -5
  156. package/build/types/networking +0 -6
  157. package/build/types/offline +0 -22
  158. package/build/types/polyfill +0 -18
  159. package/build/types/text +0 -11
  160. package/build/types/ui +0 -39
  161. package/build/updateScreenshots.py +0 -85
  162. package/build/wrapper.template.js +0 -52
@@ -1,867 +0,0 @@
1
- #!/usr/bin/env node
2
- /*! @license
3
- * Shaka Player
4
- * Copyright 2016 Google LLC
5
- * SPDX-License-Identifier: Apache-2.0
6
- */
7
-
8
- /**
9
- * @fileoverview
10
- *
11
- * A node script that generates externs automatically from the uncompiled
12
- * source of a Closure project. Designed for Shaka Player, but may be usable
13
- * in other projects as well. Does not depend on the Closure compiler itself.
14
- *
15
- * We were not able to get our externs generated by the Closure compiler. There
16
- * were many issues with the Closure-generated externs, including the order of
17
- * the externs and the replacement of record types and enums with their
18
- * underlying types.
19
- *
20
- * This uses a node module called esprima to parse JavaScript, then explores the
21
- * abstract syntax tree from esprima. It finds exported symbols and generates
22
- * an appropriate extern definition for it.
23
- *
24
- * The generated externs are then topologically sorted according to the
25
- * goog.provide and goog.require calls in the sources. No sorting is done
26
- * within source files, and no sorting is done based on parameter types.
27
- * Circular deps between source files will not be resolved, and deps not
28
- * represented in goog.provide/goog.require will not be discovered.
29
- *
30
- * Arguments: --output <EXTERNS> <INPUT> [<INPUT> ...]
31
- */
32
-
33
- // Load required modules.
34
- let assert = require('assert');
35
- if (assert.strict) {
36
- // The "strict" mode was added in v9.9, use that if available.
37
- assert = assert.strict;
38
- }
39
- const esprima = require('esprima');
40
- const fs = require('fs');
41
-
42
- // The annotations we will consider "exporting" a symbol.
43
- const EXPORT_REGEX = /@(?:export|exportInterface|expose)\b/;
44
-
45
- // TODO: revisit this when Closure Compiler supports partially-exported classes.
46
- let partiallyExportedClassesDetected = false;
47
-
48
- /**
49
- * Topological sort of general objects using a DFS approach.
50
- * Will add a __mark field to each object as part of the sorting process.
51
- * @param {!Array.<T>} list
52
- * @param {function(T):!Array.<T>} getDeps
53
- * @return {!Array.<T>}
54
- * @template T
55
- * @see https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
56
- */
57
- function topologicalSort(list, getDeps) {
58
- const sorted = [];
59
- const NOT_VISITED = 0;
60
- const MID_VISIT = 1;
61
- const COMPLETELY_VISITED = 2;
62
-
63
- // Mark all objects as not visited.
64
- for (const object of list) {
65
- object.__mark = NOT_VISITED;
66
- }
67
-
68
- // Visit each object.
69
- for (const object of list) {
70
- visit(object);
71
- }
72
-
73
- // Return the sorted list.
74
- return sorted;
75
-
76
- /**
77
- * @param {T} object
78
- * @template T
79
- */
80
- function visit(object) {
81
- if (object.__mark == MID_VISIT) {
82
- assert.fail('Dependency cycle detected!');
83
- } else if (object.__mark == NOT_VISITED) {
84
- object.__mark = MID_VISIT;
85
-
86
- // Visit all dependencies.
87
- for (const dep of getDeps(object)) {
88
- visit(dep);
89
- }
90
-
91
- object.__mark = COMPLETELY_VISITED;
92
-
93
- // Push this object onto the list. All transitive dependencies have
94
- // already been added to the list.
95
- sorted.push(object);
96
- }
97
- }
98
- }
99
-
100
- /**
101
- * @param {ASTNode} node A node from the abstract syntax tree.
102
- * @return {boolean} true if this is a call node.
103
- */
104
- function isCallNode(node) {
105
- // Example node: {
106
- // type: 'ExpressionStatement',
107
- // expression: { type: 'CallExpression', callee: {...}, arguments: [...] },
108
- // }
109
- return node.type == 'ExpressionStatement' &&
110
- node.expression.type == 'CallExpression';
111
- }
112
-
113
-
114
- /**
115
- * Pretty-print a node via console.log. Useful for debugging and development
116
- * to see what the AST looks like.
117
- * @param {ASTNode} node A node from the abstract syntax tree.
118
- */
119
- function dumpNode(node) {
120
- console.log(JSON.stringify(node, null, ' '));
121
- }
122
-
123
-
124
- /**
125
- * @param {ASTNode} node A node from the abstract syntax tree.
126
- * @return {boolean} true if this is a call to goog.provide.
127
- */
128
- function isProvideNode(node) {
129
- return isCallNode(node) &&
130
- getIdentifierString(node.expression.callee) == 'goog.provide';
131
- }
132
-
133
-
134
- /**
135
- * @param {ASTNode} node A node from the abstract syntax tree.
136
- * @return {boolean} true if this is a call to goog.require.
137
- */
138
- function isRequireNode(node) {
139
- return isCallNode(node) &&
140
- getIdentifierString(node.expression.callee) == 'goog.require';
141
- }
142
-
143
-
144
- /**
145
- * @param {ASTNode} node A node from the abstract syntax tree.
146
- * @return {boolean} true if this is an exported symbol or property.
147
- */
148
- function isExportNode(node) {
149
- const doc = getLeadingBlockComment(node);
150
- return doc && EXPORT_REGEX.test(doc);
151
- }
152
-
153
-
154
- /**
155
- * @param {ASTNode} node A node from the abstract syntax tree.
156
- * @return {boolean} true if this is a class assignment.
157
- */
158
- function isClassAssignmentNode(node) {
159
- return node.type == 'ExpressionStatement' &&
160
- node.expression.type == 'AssignmentExpression' &&
161
- node.expression.right.type == 'ClassExpression';
162
- }
163
-
164
-
165
- /**
166
- * @param {ASTNode} node A node from the abstract syntax tree.
167
- * @return {boolean} true if this is a class assignment with exported members.
168
- */
169
- function isPartiallyExportedClassAssignmentNode(node) {
170
- if (!isClassAssignmentNode(node)) {
171
- return false;
172
- }
173
-
174
- const rightSide = node.expression.right;
175
- // Example code: foo.bar = class bar2 extends foo.baz { /* ... */ };
176
- // Example right side: {
177
- // id: { name: 'bar' }, // or null
178
- // superClass: { type: 'MemberExpression', ... }, // or null
179
- // body: { body: [ ... ] },
180
- // }
181
-
182
- for (const member of rightSide.body.body) {
183
- // Only look at exported members. Constructors are exported implicitly
184
- // when the class is exported.
185
- const comment = getLeadingBlockComment(member);
186
- if (EXPORT_REGEX.test(comment)) {
187
- return true;
188
- }
189
- }
190
-
191
- return false;
192
- }
193
-
194
-
195
- /**
196
- * @param {ASTNode} node A node from the abstract syntax tree.
197
- * @return {string} A reconstructed leading comment block for the node.
198
- * If there are multiple comments before this node, we will take the most
199
- * recent block comment, as that is the one that would contain any applicable
200
- * jsdoc/closure annotations for this symbol.
201
- */
202
- function getLeadingBlockComment(node) {
203
- // Example code: /** @summary blah */ /** @export */ foo.bar = ...;
204
- // Example node: {
205
- // type: 'ExpressionStatement',
206
- // expression: { ... },
207
- // leadingComments: [
208
- // { type: 'Block', value: '* @summary blah ' },
209
- // { type: 'Block', value: '* @export ' },
210
- // ],
211
- // }
212
- if (!node.leadingComments || !node.leadingComments.length) {
213
- return null;
214
- }
215
-
216
- // Ignore non-block comments, since those are not jsdoc/Closure comments.
217
- const blockComments = node.leadingComments.filter((comment) => {
218
- return comment.type == 'Block';
219
- });
220
- if (!blockComments.length) {
221
- return null;
222
- }
223
-
224
- // In case there are multiple (for example, a file-level comment that also
225
- // preceeds the node), take the most recent one, which is closest to the node.
226
- const mostRecentComment = blockComments[blockComments.length - 1];
227
-
228
- // Reconstruct the original block comment by adding back /* and */.
229
- return '/*' + mostRecentComment.value + '*/';
230
- }
231
-
232
-
233
- /**
234
- * @param {number} idx An argument index from the call node.
235
- * @param {ASTNode} node A node from the abstract syntax tree.
236
- * @return {string} The argument value as a string.
237
- */
238
- function getArgumentFromCallNode(idx, node) {
239
- // Example node: {
240
- // type: 'ExpressionStatement',
241
- // expression: { type: 'CallExpression', callee: {...}, arguments: [...] },
242
- // }
243
- assert(isCallNode(node));
244
- return node.expression.arguments[idx].value;
245
- }
246
-
247
-
248
- /**
249
- * @param {ASTNode} node An identifier or member node from the abstract syntax
250
- * tree.
251
- * @return {string} The identifier as a string.
252
- */
253
- function getIdentifierString(node) {
254
- if (node.type == 'Identifier') {
255
- // Example code: foo
256
- // Example node: { type: 'Identifier', name: 'foo' }
257
- return node.name;
258
- }
259
-
260
- assert.equal(node.type, 'MemberExpression');
261
- // Example code: foo.bar.baz
262
- // Example node: {
263
- // type: 'MemberExpression',
264
- // object: {
265
- // type: 'MemberExpression',
266
- // object: { type: 'Identifier', name: 'foo' },
267
- // property: { type: 'Identifier', name: 'bar' },
268
- // },
269
- // property: { type: 'Identifier', name: 'baz' },
270
- // }
271
- return getIdentifierString(node.object) + '.' +
272
- getIdentifierString(node.property);
273
- }
274
-
275
-
276
- /**
277
- * @param {ASTNode} node A function definition node from the abstract syntax
278
- * tree.
279
- * @return {!Array.<string>} a list of the parameter names.
280
- */
281
- function getFunctionParameters(node) {
282
- assert(node.type == 'FunctionExpression' ||
283
- node.type == 'ArrowFunctionExpression');
284
- // Example code: function(x, y, z = null, ...varArgs) {...}
285
- // Example node: {
286
- // params: [
287
- // { type: 'Identifier', name: 'x' },
288
- // { type: 'Identifier', name: 'y' },
289
- // {
290
- // type: 'AssignmentPattern',
291
- // left: { type: 'Identifier', name: 'z' },
292
- // right: { type: 'Literal', raw: 'null' },
293
- // },
294
- // {
295
- // type: 'RestElement',
296
- // argument: { type: 'Identifier', name: 'varArgs' },
297
- // },
298
- // ],
299
- // body: {...},
300
- // }
301
- return node.params.map((param) => {
302
- if (param.type == 'Identifier') {
303
- return param.name;
304
- } else if (param.type == 'AssignmentPattern') {
305
- return param.left.name;
306
- } else {
307
- assert.equal(param.type, 'RestElement');
308
- return '...' + param.argument.name;
309
- }
310
- });
311
- }
312
-
313
-
314
- /**
315
- * Take the original block comment and prep it for the externs by removing
316
- * export annotations and blank lines.
317
- *
318
- * @param {string}
319
- * @return {string}
320
- */
321
- function removeExportAnnotationsFromComment(comment) {
322
- // Remove @export annotations.
323
- comment = comment.replace(EXPORT_REGEX, '');
324
-
325
- // Split into lines, remove empty comment lines, then recombine.
326
- comment = comment.split('\n')
327
- .filter((line) => !/^ *\*? *$/.test(line))
328
- .join('\n');
329
-
330
- return comment;
331
- }
332
-
333
-
334
- /**
335
- * Recursively find all expression statements in all block nodes.
336
- * @param {ASTNode} node
337
- * @return {!Array.<ASTNode>}
338
- */
339
- function getAllExpressionStatements(node) {
340
- assert(node.body && node.body.body);
341
- const expressionStatements = [];
342
- for (const childNode of node.body.body) {
343
- if (childNode.type == 'ExpressionStatement') {
344
- expressionStatements.push(childNode);
345
- } else if (childNode.body) {
346
- const childExpressions = getAllExpressionStatements(childNode);
347
- expressionStatements.push(...childExpressions);
348
- }
349
- }
350
- return expressionStatements;
351
- }
352
-
353
-
354
- /**
355
- * @param {!Set.<string>} names A set of the names of exported nodes.
356
- * @param {ASTNode} node An exported node from the abstract syntax tree.
357
- * @return {string} An extern string for this node.
358
- */
359
- function createExternFromExportNode(names, node) {
360
- assert.equal(node.type, 'ExpressionStatement', 'Unknown node type');
361
-
362
- let comment = getLeadingBlockComment(node);
363
- comment = removeExportAnnotationsFromComment(comment);
364
-
365
- let name;
366
- let assignment;
367
-
368
- switch (node.expression.type) {
369
- case 'AssignmentExpression':
370
- // Example code: /** @export */ foo.bar = function(...) { ... };
371
- // Example node.expression: {
372
- // operator: '=',
373
- // left: {
374
- // type: 'MemberExpression',
375
- // object: { type: 'Identifier', name: 'foo' },
376
- // property: { type: 'Identifier', name: 'bar' },
377
- // }, right: {
378
- // type: 'FunctionExpression', params: [ ... ], body: {...}
379
- // }
380
- // }
381
- name = getIdentifierString(node.expression.left);
382
- assignment = createExternAssignment(name, node.expression.right,
383
- /* alwaysIncludeConstructor= */ true);
384
- break;
385
-
386
- case 'MemberExpression':
387
- // Example code: /** @export */ foo.bar;
388
- // Example node.expression: {
389
- // object: { type: 'Identifier', name: 'foo' },
390
- // property: { type: 'Identifier', name: 'bar' },
391
- // }
392
- name = getIdentifierString(node.expression);
393
- assignment = '';
394
- break;
395
-
396
- default:
397
- assert.fail('Unexpected expression type: ' + node.expression.type);
398
- }
399
-
400
- // Keep track of the names we've externed.
401
- names.add(name);
402
- // Generate the actual extern string.
403
- let externString = comment + '\n' + name + assignment + ';\n';
404
-
405
- // Find this.foo = bar in the constructor, and potentially generate externs
406
- // for that, too.
407
- if (node.expression.type == 'AssignmentExpression') {
408
- const rightSide = node.expression.right;
409
-
410
- if (rightSide.type == 'FunctionExpression' &&
411
- comment.includes('@constructor')) {
412
- externString += createExternsFromConstructor(name, rightSide);
413
- } else if (rightSide.type == 'ClassExpression') {
414
- const ctor = getClassConstructor(node.expression.right);
415
- if (ctor) {
416
- externString += createExternsFromConstructor(name, ctor);
417
- }
418
- }
419
- }
420
- return externString;
421
- }
422
-
423
-
424
- /**
425
- * Some classes are not exported, but contain exported members. These need to
426
- * have externs generated, too.
427
- *
428
- * @param {!Set.<string>} names A set of the names of exported nodes.
429
- * @param {ASTNode} node An exported node from the abstract syntax tree.
430
- * @return {string} An extern string for this node.
431
- */
432
- function createExternFromPartiallyExportedClassAssignmentNode(names, node) {
433
- assert.equal(node.type, 'ExpressionStatement', 'Unknown node type');
434
- assert.equal(node.expression.type, 'AssignmentExpression',
435
- 'Should be assignment node');
436
- assert.equal(node.expression.right.type, 'ClassExpression',
437
- 'Should be class assignment');
438
-
439
- const name = getIdentifierString(node.expression.left);
440
- const assignment = createExternAssignment(name, node.expression.right,
441
- /* alwaysIncludeConstructor= */ false);
442
-
443
- let externString = name + assignment + ';\n';
444
-
445
- // Find this.foo = bar in the constructor, and potentially generate externs
446
- // for that, too.
447
- const rightSide = node.expression.right;
448
- const ctor = getClassConstructor(node.expression.right);
449
- if (ctor) {
450
- externString += createExternsFromConstructor(name, ctor);
451
- }
452
-
453
- // Keep track of the names we've externed.
454
- names.add(name);
455
-
456
- return externString;
457
- }
458
-
459
-
460
- /**
461
- * @param {ASTNode} node A method node from the abstract syntax tree.
462
- * @return {string} The extern string for this method.
463
- */
464
- function createExternMethod(node) {
465
- // Example code: foo.bar = class {
466
- // baz() { ... }
467
- // };
468
- // Example node: {
469
- // leadingComments: [ ... ],
470
- // static: false,
471
- // key: Identifier,
472
- // value: FunctionExpression,
473
- // }
474
- const id = getIdentifierString(node.key);
475
- let comment = getLeadingBlockComment(node);
476
- if (!comment) {
477
- if (id == 'constructor') {
478
- // ES6 constructors don't necessarily need comments; a comment along the
479
- // lines of "Creates a Foo object." doesn't really add anything.
480
- comment = '';
481
- } else {
482
- throw new Error('No leading block comment for: ' + id);
483
- }
484
- }
485
- comment = removeExportAnnotationsFromComment(comment);
486
-
487
- const params = getFunctionParameters(node.value);
488
-
489
- let methodString = (comment ? ' ' + comment + '\n' : '') + ' ';
490
- if (node.static) {
491
- methodString += 'static ';
492
- }
493
- methodString += id + '(' + params.join(', ') + ') {}';
494
- return methodString;
495
- }
496
-
497
-
498
- /**
499
- * Find the constructor of an ES6 class, if it exists.
500
- *
501
- * @param {ASTNode} className
502
- * @return {ASTNode}
503
- */
504
- function getClassConstructor(classNode) {
505
- // Example class node: {
506
- // type: 'ClassExpression',
507
- // body: {
508
- // type: 'ClassBody',
509
- // body: [ MethodDefinition, ... ],
510
- // }
511
- // }
512
- //
513
- // Example method node: {
514
- // type: 'MethodDefinition',
515
- // key: { type: 'Identifier', name: 'constructor' },
516
- // value: {
517
- // type: 'FunctionExpression',
518
- // params: [ [Identifier], [Identifier], [Identifier] ],
519
- // body: { type: 'BlockStatement', body: [Array] },
520
- // }
521
-
522
- assert.equal(classNode.type, 'ClassExpression');
523
-
524
- for (const member of classNode.body.body) {
525
- if (member.type == 'MethodDefinition' && member.key.name == 'constructor') {
526
- return member.value;
527
- }
528
- }
529
-
530
- return null;
531
- }
532
-
533
-
534
- /**
535
- * @param {string} name The name of the thing we are assigning.
536
- * @param {ASTNode} node An assignment node from the abstract syntax tree.
537
- * @param {boolean} alwaysIncludeConstructor Include the constructor of a class
538
- * expression, even if there is no export annotation.
539
- * @return {string} The assignment part of the extern string for this node.
540
- */
541
- function createExternAssignment(name, node, alwaysIncludeConstructor) {
542
- switch (node.type) {
543
- case 'ClassExpression': {
544
- // Example code: foo.bar = class bar2 extends foo.baz { /* ... */ };
545
- // Example node: {
546
- // id: { name: 'bar' }, // or null
547
- // superClass: { type: 'MemberExpression', ... }, // or null
548
- // body: { body: [ ... ] },
549
- // }
550
- let classString = ' = class ';
551
- if (node.id) {
552
- classString += getIdentifierString(node.id) + ' ';
553
- }
554
- if (node.superClass) {
555
- classString += 'extends ' + getIdentifierString(node.superClass) + ' ';
556
- }
557
- classString += '{\n';
558
- for (const member of node.body.body) {
559
- const comment = getLeadingBlockComment(member);
560
-
561
- if (EXPORT_REGEX.test(comment)) {
562
- // This has an export annotation, so fall through and generate
563
- // externs.
564
- } else {
565
- // If there's no export annotation, we may make an exception for the
566
- // constructor in some situations.
567
- if (member.key.name == 'constructor' && alwaysIncludeConstructor) {
568
- // Fall through and generate externs.
569
- } else {
570
- // Skip extern generation.
571
- continue;
572
- }
573
- }
574
-
575
- assert.equal(
576
- member.type, 'MethodDefinition',
577
- 'Unexpected exported member type in exported class!');
578
-
579
- classString += createExternMethod(member) + '\n';
580
- }
581
- classString += '}';
582
- return classString;
583
- }
584
-
585
- case 'ArrowFunctionExpression':
586
- case 'FunctionExpression': {
587
- // Example code: foo.square = function(x) { return x * x; };
588
- // Example node: { params: [ { type: 'Identifier', name: 'x' } ] }
589
- const params = getFunctionParameters(node);
590
- return ' = function(' + params.join(', ') + ') {}';
591
- }
592
-
593
- case 'ObjectExpression': {
594
- // Example code: foo.Bar = { 'ABC': 1, DEF: 2 };
595
- // Example node: {
596
- // properties: [ {
597
- // kind: 'init',
598
- // key: { type: 'Literal', value: 'ABC' }
599
- // value: { type: 'Literal', value: 1 }
600
- // }, {
601
- // kind: 'init',
602
- // key: { type: 'Identifier', name: 'DEF' }
603
- // value: { type: 'Literal', value: 2 }
604
- // } ]
605
- // }
606
- const propertyStrings = node.properties.map((prop) => {
607
- assert.equal(prop.kind, 'init');
608
- assert(prop.key.type == 'Literal' || prop.key.type == 'Identifier');
609
- // Literal indicates a quoted name in the source, while Identifier is
610
- // an unquoted name. In the case of Literal, key.raw gets us the
611
- // unquoted name, we end up with an unquoted name in both cases.
612
- const name = prop.key.type == 'Literal' ? prop.key.raw : prop.key.name;
613
- assert.equal(prop.value.type, 'Literal');
614
- return ' ' + name + ': ' + prop.value.raw;
615
- });
616
- return ' = {\n' + propertyStrings.join(',\n') + '\n}';
617
- }
618
-
619
- case 'Identifier':
620
- // Example code: /** @const {string} @export */ foo.version = VERSION;
621
- // Example extern: /** @const {string} */ foo.version;
622
- return '';
623
-
624
- case 'Literal':
625
- // Example code: /** @const {string} @export */ foo.version = 'v1.0.0';
626
- // Example extern: /** @const {string} */ foo.version;
627
- return '';
628
-
629
- default:
630
- assert.fail('Unexpected export type: ' + node.type);
631
- return ''; // Shouldn't be hit, but linter wants a return statement.
632
- }
633
- }
634
-
635
-
636
- /**
637
- * Look for exports in a constructor body. If we don't do this, we may end up
638
- * with errors about classes not fully implementing their interfaces. In
639
- * reality, the interface is implemented by assigning members on "this".
640
- *
641
- * @param {string} className
642
- * @param {ASTNode} constructorNode
643
- * @return {string}
644
- */
645
- function createExternsFromConstructor(className, constructorNode) {
646
- // Example code:
647
- //
648
- // /** @interface @exportInterface */
649
- // FooLike = function() {};
650
- //
651
- // /** @exportInterface @type {number} */
652
- // FooLike.prototype.bar;
653
- //
654
- // /** @export @implements {FooLike} */
655
- // class Foo {
656
- // constructor() {
657
- // /** @override @exportInterface */
658
- // this.bar = 10;
659
- // }
660
- // };
661
- //
662
- // Example externs:
663
- //
664
- // /**
665
- // * Generated by createExternFromExportNode:
666
- // * @implements {FooLike}
667
- // */
668
- // class Foo {
669
- // constructor() {}
670
- // }
671
- //
672
- // /**
673
- // * Generated by createExternsFromConstructor:
674
- // * @override
675
- // */
676
- // Foo.prototype.bar;
677
-
678
- const expressionStatements = getAllExpressionStatements(constructorNode);
679
- let externString = '';
680
-
681
- for (const statement of expressionStatements) {
682
- const left = statement.expression.left;
683
- const right = statement.expression.right;
684
-
685
- // Skip anything that isn't an assignment to a member of "this".
686
- if (statement.expression.type != 'AssignmentExpression' ||
687
- left.type != 'MemberExpression' ||
688
- left.object.type != 'ThisExpression') {
689
- continue;
690
- }
691
-
692
- assert(left);
693
- assert(right);
694
-
695
- // Skip anything that isn't exported.
696
- let comment = getLeadingBlockComment(statement);
697
- if (!EXPORT_REGEX.test(comment)) {
698
- continue;
699
- }
700
-
701
- comment = removeExportAnnotationsFromComment(comment);
702
-
703
- assert.equal(left.property.type, 'Identifier');
704
- const name = className + '.prototype.' + left.property.name;
705
- externString += comment + '\n' + name + ';\n';
706
- }
707
-
708
- return externString;
709
- }
710
-
711
-
712
- /**
713
- * @param {!Set.<string>} names A set of the names of exported nodes.
714
- * @param {string} inputPath
715
- * @return {{
716
- * path: string,
717
- * provides: !Array.<string>,
718
- * requires: !Array.<string>,
719
- * externs: string,
720
- * }}
721
- */
722
- function generateExterns(names, inputPath) {
723
- // Load and parse the code, with comments attached to the nodes.
724
- const code = fs.readFileSync(inputPath, 'utf-8');
725
- const program = esprima.parse(code, {attachComment: true});
726
- assert.equal(program.type, 'Program');
727
-
728
- const body = program.body;
729
- const provides = program.body.filter(isProvideNode)
730
- .map((node) => getArgumentFromCallNode(0, node));
731
- const requires = program.body.filter(isRequireNode)
732
- .map((node) => getArgumentFromCallNode(0, node));
733
-
734
- // Get all exported nodes and all classes, in order.
735
- const rawExterns = program.body.map((node) => {
736
- if (isExportNode(node)) {
737
- // Explicitly-exported nodes are handled here.
738
- return createExternFromExportNode(names, node);
739
- } else if (isPartiallyExportedClassAssignmentNode(node)) {
740
- // Some classes are not exported, but contain exported members. These
741
- // need to have externs generated, too.
742
-
743
- // But wait! The latest compiler won't actually export those correctly!
744
- // TODO: File a bug against the Closure Compiler.
745
- // In the mean time, log these now and throw an error at the end to make
746
- // sure we are generating usable releases. This tends to affect our
747
- // plugin registration APIs, and apps should definitely be able to use
748
- // those!
749
- if (!partiallyExportedClassesDetected) {
750
- partiallyExportedClassesDetected = true;
751
- console.log('The Closure Compiler does not handle partially-exported ' +
752
- 'classes correctly! The following classes need to be exported:');
753
- }
754
-
755
- const name = getIdentifierString(node.expression.left);
756
- console.log(' * ' + name);
757
-
758
- return createExternFromPartiallyExportedClassAssignmentNode(names, node);
759
- } else {
760
- // Ignore anything else, and don't generate any externs.
761
- return '';
762
- }
763
- });
764
-
765
- const externs = rawExterns.join('');
766
-
767
- return {
768
- path: inputPath,
769
- provides: provides,
770
- requires: requires,
771
- externs: externs,
772
- };
773
- }
774
-
775
-
776
- /**
777
- * Generate externs from exported code.
778
- * Arguments: --output <EXTERNS> <INPUT> [<INPUT> ...]
779
- *
780
- * @param {!Array.<string>} args The args to this script, not counting node and
781
- * the script name itself.
782
- */
783
- function main(args) {
784
- const inputPaths = [];
785
- let outputPath;
786
-
787
- for (let i = 0; i < args.length; ++i) {
788
- if (args[i] == '--output') {
789
- outputPath = args[i + 1];
790
- ++i;
791
- } else {
792
- inputPaths.push(args[i]);
793
- }
794
- }
795
- assert(outputPath, 'You must specify output file with --output <EXTERNS>');
796
- assert(inputPaths.length, 'You must specify at least one input file.');
797
-
798
- // Generate externs for all input paths.
799
- const names = new Set();
800
- const results = inputPaths.map((path) => generateExterns(names, path));
801
-
802
- // TODO: revisit this when the compiler supports partially-exported classes.
803
- if (partiallyExportedClassesDetected) {
804
- throw new Error(
805
- 'Partially exported classes are not supported in the compiler!');
806
- }
807
-
808
- // Sort them in dependency order.
809
- const sorted = topologicalSort(results, /* getDeps= */ (object) => {
810
- return object.requires.map((id) => {
811
- const dep = results.find((x) => x.provides.includes(id));
812
- assert(dep, 'Cannot find dependency: ' + id);
813
- return dep;
814
- });
815
- });
816
-
817
- // Generate namespaces for all externs. For example, if we extern
818
- // foo.bar.baz, foo and foo.bar will both need to be declared first.
819
- const namespaces = new Set();
820
- const namespaceDeclarations = [];
821
- for (const name of Array.from(names).sort()) {
822
- // Add the full name "foo.bar.baz" and its prototype ahead of time. We
823
- // should never generate these as namespaces.
824
- namespaces.add(name);
825
- namespaces.add(name + '.prototype');
826
-
827
- // For name "foo.bar.baz", iterate over partialName "foo" and "foo.bar".
828
- const pieces = name.split('.');
829
- for (let i = 1; i < pieces.length; ++i) {
830
- const partialName = pieces.slice(0, i).join('.');
831
- if (!namespaces.has(partialName)) {
832
- let declaration;
833
- if (i == 1) {
834
- declaration = '/** @namespace */\n';
835
- declaration += 'window.';
836
- } else {
837
- declaration = '/** @const */\n';
838
- }
839
- declaration += partialName + ' = {};\n';
840
- namespaceDeclarations.push(declaration);
841
- namespaces.add(partialName);
842
- }
843
- }
844
- }
845
-
846
- // Get externs.
847
- const externs = sorted.map((x) => x.externs).join('');
848
-
849
- // Get license header.
850
- const licenseHeader = fs.readFileSync(__dirname + '/license-header', 'utf-8');
851
-
852
- // Output generated externs, with an appropriate header.
853
- fs.writeFileSync(outputPath,
854
- licenseHeader +
855
- '/**\n' +
856
- ' * @fileoverview Generated externs. DO NOT EDIT!\n' +
857
- ' * @externs\n' +
858
- ' * @suppress {duplicate} To prevent compiler errors with the\n' +
859
- ' * namespace being declared both here and by goog.provide in the\n' +
860
- ' * library.\n' +
861
- ' */\n\n' +
862
- namespaceDeclarations.join('') + '\n' + externs);
863
- }
864
-
865
-
866
- // Skip argv[0], which is the node binary, and argv[1], which is the script.
867
- main(process.argv.slice(2));