index.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import * as _acorn from "acorn";
  2. const leftCurlyBrace = "{".charCodeAt(0);
  3. const space = " ".charCodeAt(0);
  4. const withKeyword = "with";
  5. const assertKeyword = "assert";
  6. const FUNC_STATEMENT = 1, FUNC_HANGING_STATEMENT = 2, FUNC_NULLABLE_ID = 4
  7. export const importAttributes = plugin({ keyword: "with" });
  8. export const importAssertions = plugin({ keyword: "assert" });
  9. export const importAttributesOrAssertions = plugin({ keyword: "with-assert" })
  10. function plugin(options) {
  11. return function(Parser) {
  12. return pluginImpl(options, Parser);
  13. };
  14. }
  15. function pluginImpl(options, Parser) {
  16. // Use supplied version acorn version if present, to avoid
  17. // reference mismatches due to different acorn versions. This
  18. // allows this plugin to be used with Rollup which supplies
  19. // its own internal version of acorn and thereby sidesteps
  20. // the package manager.
  21. const acorn = Parser.acorn || _acorn;
  22. const { tokTypes: tt, TokenType } = acorn;
  23. const { keyword } = options;
  24. const isWithKeyword = keyword.includes(withKeyword);
  25. const isAssertKeyword = keyword.includes(assertKeyword);
  26. const isWithOrAssertKeyword = isWithKeyword && isAssertKeyword;
  27. return class extends Parser {
  28. constructor(...args) {
  29. super(...args);
  30. this.withToken = isWithKeyword && new TokenType(withKeyword);
  31. this.assertToken = isAssertKeyword && new TokenType(assertKeyword);
  32. }
  33. _codeAt(i) {
  34. return this.input.charCodeAt(i);
  35. }
  36. _eat(t) {
  37. if (this.type !== t) {
  38. this.unexpected();
  39. }
  40. this.next();
  41. }
  42. _matchKeywordToken() {
  43. return (isWithOrAssertKeyword && (this.type === this.withToken || this.type === this.assertToken))
  44. || (isWithKeyword && this.type === this.withToken)
  45. || (isAssertKeyword && this.type === this.assertToken)
  46. }
  47. _getProperty() {
  48. if (isWithOrAssertKeyword) {
  49. return this.type === this.withToken ? "attributes" : "assertions";
  50. }
  51. return isWithKeyword ? "attributes" : "assertions";
  52. }
  53. readToken(code) {
  54. let i = 0;
  55. let keyword;
  56. let token;
  57. if (isWithOrAssertKeyword) {
  58. if (this.input.slice(this.pos, this.pos + withKeyword.length) === withKeyword) {
  59. keyword = withKeyword;
  60. token = this.withToken;
  61. } else if (this.input.slice(this.pos, this.pos + assertKeyword.length) === assertKeyword) {
  62. keyword = assertKeyword;
  63. token = this.assertToken;
  64. } else {
  65. return super.readToken(code);
  66. }
  67. i += keyword.length;
  68. } else {
  69. keyword = isWithKeyword ? withKeyword : assertKeyword;
  70. token = isWithKeyword ? this.withToken : this.assertToken;
  71. for (; i < keyword.length; i++) {
  72. if (this._codeAt(this.pos + i) !== keyword.charCodeAt(i)) {
  73. return super.readToken(code);
  74. }
  75. }
  76. }
  77. // ensure that the keyword is at the correct location
  78. // ie `with{...` or `with {...`
  79. for (;; i++) {
  80. if (this._codeAt(this.pos + i) === leftCurlyBrace) {
  81. // Found '{'
  82. break;
  83. } else if (this._codeAt(this.pos + i) === space) {
  84. // white space is allowed between `with` and `{`, so continue.
  85. continue;
  86. } else {
  87. return super.readToken(code);
  88. }
  89. }
  90. // If we're inside a dynamic import expression we'll parse
  91. // the `with` keyword as a standard object property name
  92. // ie `import(""./foo.json", { with: { type: "json" } })`
  93. if (this.type.label === "{") {
  94. return super.readToken(code);
  95. }
  96. this.pos += keyword.length;
  97. return this.finishToken(token);
  98. }
  99. parseDynamicImport(node) {
  100. this.next(); // skip `(`
  101. // Parse node.source.
  102. node.source = this.parseMaybeAssign();
  103. if (this.eat(tt.comma)) {
  104. const expr = this.parseExpression();
  105. node.arguments = [expr];
  106. }
  107. this._eat(tt.parenR);
  108. return this.finishNode(node, "ImportExpression");
  109. }
  110. // ported from acorn/src/statement.js pp.parseExport
  111. parseExport(node, exports) {
  112. this.next();
  113. // export * from '...'
  114. if (this.eat(tt.star)) {
  115. if (this.options.ecmaVersion >= 11) {
  116. if (this.eatContextual("as")) {
  117. node.exported = this.parseIdent(true);
  118. this.checkExport(exports, node.exported.name, this.lastTokStart);
  119. } else {
  120. node.exported = null;
  121. }
  122. }
  123. this.expectContextual("from");
  124. if (this.type !== tt.string) { this.unexpected(); }
  125. node.source = this.parseExprAtom();
  126. if (this._matchKeywordToken()) {
  127. const property = this._getProperty();
  128. this.next();
  129. const attributes = this.parseImportAttributes();
  130. if (attributes) {
  131. node[property] = attributes;
  132. }
  133. }
  134. this.semicolon();
  135. return this.finishNode(node, "ExportAllDeclaration")
  136. }
  137. if (this.eat(tt._default)) { // export default ...
  138. this.checkExport(exports, "default", this.lastTokStart);
  139. var isAsync;
  140. if (this.type === tt._function || (isAsync = this.isAsyncFunction())) {
  141. var fNode = this.startNode();
  142. this.next();
  143. if (isAsync) { this.next(); }
  144. node.declaration = this.parseFunction(fNode, FUNC_STATEMENT | FUNC_NULLABLE_ID, false, isAsync);
  145. } else if (this.type === tt._class) {
  146. var cNode = this.startNode();
  147. node.declaration = this.parseClass(cNode, "nullableID");
  148. } else {
  149. node.declaration = this.parseMaybeAssign();
  150. this.semicolon();
  151. }
  152. return this.finishNode(node, "ExportDefaultDeclaration")
  153. }
  154. // export var|const|let|function|class ...
  155. if (this.shouldParseExportStatement()) {
  156. node.declaration = this.parseStatement(null);
  157. if (node.declaration.type === "VariableDeclaration")
  158. { this.checkVariableExport(exports, node.declaration.declarations); }
  159. else
  160. { this.checkExport(exports, node.declaration.id.name, node.declaration.id.start); }
  161. node.specifiers = [];
  162. node.source = null;
  163. } else { // export { x, y as z } [from '...']
  164. node.declaration = null;
  165. node.specifiers = this.parseExportSpecifiers(exports);
  166. if (this.eatContextual("from")) {
  167. if (this.type !== tt.string) { this.unexpected(); }
  168. node.source = this.parseExprAtom();
  169. if (this._matchKeywordToken()) {
  170. const property = this._getProperty();
  171. this.next();
  172. const attributes = this.parseImportAttributes();
  173. if (attributes) {
  174. node[property] = attributes;
  175. }
  176. }
  177. } else {
  178. for (var i = 0, list = node.specifiers; i < list.length; i += 1) {
  179. // check for keywords used as local names
  180. var spec = list[i];
  181. this.checkUnreserved(spec.local);
  182. // check if export is defined
  183. this.checkLocalExport(spec.local);
  184. }
  185. node.source = null;
  186. }
  187. this.semicolon();
  188. }
  189. return this.finishNode(node, "ExportNamedDeclaration")
  190. }
  191. parseImport(node) {
  192. this.next();
  193. // import '...'
  194. if (this.type === tt.string) {
  195. node.specifiers = [];
  196. node.source = this.parseExprAtom();
  197. } else {
  198. node.specifiers = this.parseImportSpecifiers();
  199. this.expectContextual("from");
  200. node.source =
  201. this.type === tt.string ? this.parseExprAtom() : this.unexpected();
  202. }
  203. if (this._matchKeywordToken()) {
  204. const property = this._getProperty();
  205. this.next();
  206. const attributes = this.parseImportAttributes();
  207. if (attributes) {
  208. node[property] = attributes;
  209. }
  210. }
  211. this.semicolon();
  212. return this.finishNode(node, "ImportDeclaration");
  213. }
  214. parseImportAttributes() {
  215. this._eat(tt.braceL);
  216. const attrs = this.parsewithEntries();
  217. this._eat(tt.braceR);
  218. return attrs;
  219. }
  220. parsewithEntries() {
  221. const attrs = [];
  222. const attrNames = new Set();
  223. do {
  224. if (this.type === tt.braceR) {
  225. break;
  226. }
  227. const node = this.startNode();
  228. // parse withionKey : IdentifierName, StringLiteral
  229. let withionKeyNode;
  230. if (this.type === tt.string) {
  231. withionKeyNode = this.parseLiteral(this.value);
  232. } else {
  233. withionKeyNode = this.parseIdent(true);
  234. }
  235. this.next();
  236. node.key = withionKeyNode;
  237. // check if we already have an entry for an attribute
  238. // if a duplicate entry is found, throw an error
  239. // for now this logic will come into play only when someone declares `type` twice
  240. if (attrNames.has(node.key.name)) {
  241. this.raise(this.pos, "Duplicated key in attributes");
  242. }
  243. attrNames.add(node.key.name);
  244. if (this.type !== tt.string) {
  245. this.raise(
  246. this.pos,
  247. "Only string is supported as an attribute value"
  248. );
  249. }
  250. node.value = this.parseLiteral(this.value);
  251. attrs.push(this.finishNode(node, "ImportAttribute"));
  252. } while (this.eat(tt.comma));
  253. return attrs;
  254. }
  255. };
  256. }