gooderp18绿色标准版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

752 lines
25KB

  1. """
  2. This code is what let us use ES6-style modules in odoo.
  3. Classic Odoo modules are composed of a top-level :samp:`odoo.define({name},{dependencies},{body_function})` call.
  4. This processor will take files starting with an `@odoo-module` annotation (in a comment) and convert them to classic modules.
  5. If any file has the ``/** odoo-module */`` on top of it, it will get processed by this class.
  6. It performs several operations to get from ES6 syntax to the usual odoo one with minimal changes.
  7. This is done on the fly, this not a pre-processing tool.
  8. Caveat: This is done without a full parser, only using regex. One can only expect to cover as much edge cases
  9. as possible with reasonable limitations. Also, this only changes imports and exports, so all JS features used in
  10. the original source need to be supported by the browsers.
  11. """
  12. import re
  13. import logging
  14. from functools import partial
  15. from odoo.tools.misc import OrderedSet
  16. _logger = logging.getLogger(__name__)
  17. def transpile_javascript(url, content):
  18. """
  19. Transpile the code from native JS modules to custom odoo modules.
  20. :param content: The original source code
  21. :param url: The url of the file in the project
  22. :return: The transpiled source code
  23. """
  24. module_path = url_to_module_path(url)
  25. legacy_odoo_define = get_aliased_odoo_define_content(module_path, content)
  26. dependencies = OrderedSet()
  27. # The order of the operations does sometimes matter.
  28. steps = [
  29. convert_legacy_default_import,
  30. convert_basic_import,
  31. convert_default_and_named_import,
  32. convert_default_and_star_import,
  33. convert_default_import,
  34. convert_star_import,
  35. convert_unnamed_relative_import,
  36. convert_from_export,
  37. convert_star_from_export,
  38. remove_index,
  39. partial(convert_relative_require, url, dependencies),
  40. convert_export_function,
  41. convert_export_class,
  42. convert_variable_export,
  43. convert_object_export,
  44. convert_default_export,
  45. partial(wrap_with_qunit_module, url),
  46. partial(wrap_with_odoo_define, module_path, dependencies),
  47. ]
  48. for s in steps:
  49. content = s(content)
  50. if legacy_odoo_define:
  51. content += legacy_odoo_define
  52. return content
  53. URL_RE = re.compile(r"""
  54. /?(?P<module>\S+) # /module name
  55. /([\S/]*/)?static/ # ... /static/
  56. (?P<type>src|tests|lib) # src, test, or lib file
  57. (?P<url>/[\S/]*) # URL (/...)
  58. """, re.VERBOSE)
  59. def url_to_module_path(url):
  60. """
  61. Odoo modules each have a name. (odoo.define("<the name>", [<dependencies>], function (require) {...});
  62. It is used in to be required later. (const { something } = require("<the name>").
  63. The transpiler transforms the url of the file in the project to this name.
  64. It takes the module name and add a @ on the start of it, and map it to be the source of the static/src (or
  65. static/tests, or static/lib) folder in that module.
  66. in: web/static/src/one/two/three.js
  67. out: @web/one/two/three.js
  68. The module would therefore be defined and required by this path.
  69. :param url: an url in the project
  70. :return: a special path starting with @<module-name>.
  71. """
  72. match = URL_RE.match(url)
  73. if match:
  74. url = match["url"]
  75. if url.endswith(('/index.js', '/index')):
  76. url, _ = url.rsplit('/', 1)
  77. if url.endswith('.js'):
  78. url = url[:-3]
  79. if match["type"] == "src":
  80. return "@%s%s" % (match['module'], url)
  81. elif match["type"] == "lib":
  82. return "@%s/../lib%s" % (match['module'], url)
  83. else:
  84. return "@%s/../tests%s" % (match['module'], url)
  85. else:
  86. raise ValueError("The js file %r must be in the folder '/static/src' or '/static/lib' or '/static/test'" % url)
  87. def wrap_with_qunit_module(url, content):
  88. """
  89. Wraps the test file content (source code) with the QUnit.module('module_name', function() {...}).
  90. """
  91. if "tests" in url and re.search(r'QUnit\.(test|debug|only)\(', content):
  92. match = URL_RE.match(url)
  93. return f"""QUnit.module("{match["module"]}", function() {{{content}}});"""
  94. else:
  95. return content
  96. def wrap_with_odoo_define(module_path, dependencies, content):
  97. """
  98. Wraps the current content (source code) with the odoo.define call.
  99. It adds as a second argument the list of dependencies.
  100. Should logically be called once all other operations have been performed.
  101. """
  102. return f"""odoo.define({module_path!r}, {list(dependencies)}, function (require) {{
  103. 'use strict';
  104. let __exports = {{}};
  105. {content}
  106. return __exports;
  107. }});
  108. """
  109. EXPORT_FCT_RE = re.compile(r"""
  110. ^
  111. (?P<space>\s*) # space and empty line
  112. export\s+ # export
  113. (?P<type>(async\s+)?function)\s+ # async function or function
  114. (?P<identifier>\w+) # name the function
  115. """, re.MULTILINE | re.VERBOSE)
  116. def convert_export_function(content):
  117. """
  118. Transpile functions that are being exported.
  119. .. code-block:: javascript
  120. // before
  121. export function name
  122. // after
  123. __exports.name = name; function name
  124. // before
  125. export async function name
  126. // after
  127. __exports.name = name; async function name
  128. """
  129. repl = r"\g<space>__exports.\g<identifier> = \g<identifier>; \g<type> \g<identifier>"
  130. return EXPORT_FCT_RE.sub(repl, content)
  131. EXPORT_CLASS_RE = re.compile(r"""
  132. ^
  133. (?P<space>\s*) # space and empty line
  134. export\s+ # export
  135. (?P<type>class)\s+ # class
  136. (?P<identifier>\w+) # name of the class
  137. """, re.MULTILINE | re.VERBOSE)
  138. def convert_export_class(content):
  139. """
  140. Transpile classes that are being exported.
  141. .. code-block:: javascript
  142. // before
  143. export class name
  144. // after
  145. const name = __exports.name = class name
  146. """
  147. repl = r"\g<space>const \g<identifier> = __exports.\g<identifier> = \g<type> \g<identifier>"
  148. return EXPORT_CLASS_RE.sub(repl, content)
  149. EXPORT_FCT_DEFAULT_RE = re.compile(r"""
  150. ^
  151. (?P<space>\s*) # space and empty line
  152. export\s+default\s+ # export default
  153. (?P<type>(async\s+)?function)\s+ # async function or function
  154. (?P<identifier>\w+) # name of the function
  155. """, re.MULTILINE | re.VERBOSE)
  156. def convert_export_function_default(content):
  157. """
  158. Transpile functions that are being exported as default value.
  159. .. code-block:: javascript
  160. // before
  161. export default function name
  162. // after
  163. __exports[Symbol.for("default")] = name; function name
  164. // before
  165. export default async function name
  166. // after
  167. __exports[Symbol.for("default")] = name; async function name
  168. """
  169. repl = r"""\g<space>__exports[Symbol.for("default")] = \g<identifier>; \g<type> \g<identifier>"""
  170. return EXPORT_FCT_DEFAULT_RE.sub(repl, content)
  171. EXPORT_CLASS_DEFAULT_RE = re.compile(r"""
  172. ^
  173. (?P<space>\s*) # space and empty line
  174. export\s+default\s+ # export default
  175. (?P<type>class)\s+ # class
  176. (?P<identifier>\w+) # name of the class or the function
  177. """, re.MULTILINE | re.VERBOSE)
  178. def convert_export_class_default(content):
  179. """
  180. Transpile classes that are being exported as default value.
  181. .. code-block:: javascript
  182. // before
  183. export default class name
  184. // after
  185. const name = __exports[Symbol.for("default")] = class name
  186. """
  187. repl = r"""\g<space>const \g<identifier> = __exports[Symbol.for("default")] = \g<type> \g<identifier>"""
  188. return EXPORT_CLASS_DEFAULT_RE.sub(repl, content)
  189. EXPORT_VAR_RE = re.compile(r"""
  190. ^
  191. (?P<space>\s*) # space and empty line
  192. export\s+ # export
  193. (?P<type>let|const|var)\s+ # let or cont or var
  194. (?P<identifier>\w+) # variable name
  195. """, re.MULTILINE | re.VERBOSE)
  196. def convert_variable_export(content):
  197. """
  198. Transpile variables that are being exported.
  199. .. code-block:: javascript
  200. // before
  201. export let name
  202. // after
  203. let name = __exports.name
  204. // (same with var and const)
  205. """
  206. repl = r"\g<space>\g<type> \g<identifier> = __exports.\g<identifier>"
  207. return EXPORT_VAR_RE.sub(repl, content)
  208. EXPORT_DEFAULT_VAR_RE = re.compile(r"""
  209. ^
  210. (?P<space>\s*) # space and empty line
  211. export\s+default\s+ # export default
  212. (?P<type>let|const|var)\s+ # let or const or var
  213. (?P<identifier>\w+)\s* # variable name
  214. """, re.MULTILINE | re.VERBOSE)
  215. def convert_variable_export_default(content):
  216. """
  217. Transpile the variables that are exported as default values.
  218. .. code-block:: javascript
  219. // before
  220. export default let name
  221. // after
  222. let name = __exports[Symbol.for("default")]
  223. """
  224. repl = r"""\g<space>\g<type> \g<identifier> = __exports[Symbol.for("default")]"""
  225. return EXPORT_DEFAULT_VAR_RE.sub(repl, content)
  226. EXPORT_OBJECT_RE = re.compile(r"""
  227. ^
  228. (?P<space>\s*) # space and empty line
  229. export\s* # export
  230. (?P<object>{[\w\s,]+}) # { a, b, c as x, ... }
  231. """, re.MULTILINE | re.VERBOSE)
  232. def convert_object_export(content):
  233. """
  234. Transpile exports of multiple elements
  235. .. code-block:: javascript
  236. // before
  237. export { a, b, c as x }
  238. // after
  239. Object.assign(__exports, { a, b, x: c })
  240. """
  241. def repl(matchobj):
  242. object_process = "{" + ", ".join([convert_as(val) for val in matchobj["object"][1:-1].split(",")]) + "}"
  243. space = matchobj["space"]
  244. return f"{space}Object.assign(__exports, {object_process})"
  245. return EXPORT_OBJECT_RE.sub(repl, content)
  246. EXPORT_FROM_RE = re.compile(r"""
  247. ^
  248. (?P<space>\s*) # space and empty line
  249. export\s* # export
  250. (?P<object>{[\w\s,]+})\s* # { a, b, c as x, ... }
  251. from\s* # from
  252. (?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path.js")
  253. """, re.MULTILINE | re.VERBOSE)
  254. def convert_from_export(content):
  255. """
  256. Transpile exports coming from another source
  257. .. code-block:: javascript
  258. // before
  259. export { a, b, c as x } from "some/path.js"
  260. // after
  261. { a, b, c } = {require("some/path.js"); Object.assign(__exports, { a, b, x: c });}
  262. """
  263. def repl(matchobj):
  264. object_clean = "{" + ",".join([remove_as(val) for val in matchobj["object"][1:-1].split(",")]) + "}"
  265. object_process = "{" + ", ".join([convert_as(val) for val in matchobj["object"][1:-1].split(",")]) + "}"
  266. return "%(space)s{const %(object_clean)s = require(%(path)s);Object.assign(__exports, %(object_process)s)}" % {
  267. 'object_clean': object_clean,
  268. 'object_process': object_process,
  269. 'space': matchobj['space'],
  270. 'path': matchobj['path'],
  271. }
  272. return EXPORT_FROM_RE.sub(repl, content)
  273. EXPORT_STAR_FROM_RE = re.compile(r"""
  274. ^
  275. (?P<space>\s*) # space and empty line
  276. export\s*\*\s*from\s* # export * from
  277. (?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path.js")
  278. """, re.MULTILINE | re.VERBOSE)
  279. def convert_star_from_export(content):
  280. """
  281. Transpile exports star coming from another source
  282. .. code-block:: javascript
  283. // before
  284. export * from "some/path.js"
  285. // after
  286. Object.assign(__exports, require("some/path.js"))
  287. """
  288. repl = r"\g<space>Object.assign(__exports, require(\g<path>))"
  289. return EXPORT_STAR_FROM_RE.sub(repl, content)
  290. EXPORT_DEFAULT_RE = re.compile(r"""
  291. ^
  292. (?P<space>\s*) # space and empty line
  293. export\s+default # export default
  294. (\s+\w+\s*=)? # something (optional)
  295. """, re.MULTILINE | re.VERBOSE)
  296. def convert_default_export(content):
  297. """
  298. This function handles the default exports.
  299. Either by calling another operation with a TRUE flag, and if any default is left, doing a simple replacement.
  300. (see convert_export_function_or_class_default and convert_variable_export_default).
  301. +
  302. .. code-block:: javascript
  303. // before
  304. export default
  305. // after
  306. __exports[Symbol.for("default")] =
  307. .. code-block:: javascript
  308. // before
  309. export default something =
  310. // after
  311. __exports[Symbol.for("default")] =
  312. """
  313. new_content = convert_export_function_default(content)
  314. new_content = convert_export_class_default(new_content)
  315. new_content = convert_variable_export_default(new_content)
  316. repl = r"""\g<space>__exports[Symbol.for("default")] ="""
  317. return EXPORT_DEFAULT_RE.sub(repl, new_content)
  318. IMPORT_BASIC_RE = re.compile(r"""
  319. ^
  320. (?P<space>\s*) # space and empty line
  321. import\s+ # import
  322. (?P<object>{[\s\w,]+})\s* # { a, b, c as x, ... }
  323. from\s* # from
  324. (?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path")
  325. """, re.MULTILINE | re.VERBOSE)
  326. def convert_basic_import(content):
  327. """
  328. Transpile the simpler import call.
  329. .. code-block:: javascript
  330. // before
  331. import { a, b, c as x } from "some/path"
  332. // after
  333. const {a, b, c: x} = require("some/path")
  334. """
  335. def repl(matchobj):
  336. new_object = matchobj["object"].replace(" as ", ": ")
  337. return f"{matchobj['space']}const {new_object} = require({matchobj['path']})"
  338. return IMPORT_BASIC_RE.sub(repl, content)
  339. IMPORT_LEGACY_DEFAULT_RE = re.compile(r"""
  340. ^
  341. (?P<space>\s*) # space and empty line
  342. import\s+ # import
  343. (?P<identifier>\w+)\s* # default variable name
  344. from\s* # from
  345. (?P<path>(?P<quote>["'`])([^@\."'`][^"'`]*)(?P=quote)) # legacy alias file ("addon_name.module_name" or "some/path")
  346. """, re.MULTILINE | re.VERBOSE)
  347. def convert_legacy_default_import(content):
  348. """
  349. Transpile legacy imports (that were used as they were default import).
  350. Legacy imports means that their name is not a path but a <addon_name>.<module_name>.
  351. It requires slightly different processing.
  352. .. code-block:: javascript
  353. // before
  354. import module_name from "addon.module_name"
  355. // after
  356. const module_name = require("addon.module_name")
  357. """
  358. repl = r"""\g<space>const \g<identifier> = require(\g<path>)"""
  359. return IMPORT_LEGACY_DEFAULT_RE.sub(repl, content)
  360. IMPORT_DEFAULT = re.compile(r"""
  361. ^
  362. (?P<space>\s*) # space and empty line
  363. import\s+ # import
  364. (?P<identifier>\w+)\s* # default variable name
  365. from\s* # from
  366. (?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path")
  367. """, re.MULTILINE | re.VERBOSE)
  368. def convert_default_import(content):
  369. """
  370. Transpile the default import call.
  371. .. code-block:: javascript
  372. // before
  373. import something from "some/path"
  374. // after
  375. const something = require("some/path")[Symbol.for("default")]
  376. """
  377. repl = r"""\g<space>const \g<identifier> = require(\g<path>)[Symbol.for("default")]"""
  378. return IMPORT_DEFAULT.sub(repl, content)
  379. IS_PATH_LEGACY_RE = re.compile(r"""(?P<quote>["'`])([^@\."'`][^"'`]*)(?P=quote)""")
  380. IMPORT_DEFAULT_AND_NAMED_RE = re.compile(r"""
  381. ^
  382. (?P<space>\s*) # space and empty line
  383. import\s+ # import
  384. (?P<default_export>\w+)\s*,\s* # default variable name,
  385. (?P<named_exports>{[\s\w,]+})\s* # { a, b, c as x, ... }
  386. from\s* # from
  387. (?P<path>(?P<quote>["'`])([^"'`]+)(?P=quote)) # "file path" ("some/path")
  388. """, re.MULTILINE | re.VERBOSE)
  389. def convert_default_and_named_import(content):
  390. """
  391. Transpile default and named import on one line.
  392. .. code-block:: javascript
  393. // before
  394. import something, { a } from "some/path";
  395. import somethingElse, { b } from "legacy.module";
  396. // after
  397. const { [Symbol.for("default")]: something, a } = require("some/path");
  398. const somethingElse = require("legacy.module");
  399. const { b } = somethingElse;
  400. """
  401. def repl(matchobj):
  402. is_legacy = IS_PATH_LEGACY_RE.match(matchobj['path'])
  403. new_object = matchobj["named_exports"].replace(" as ", ": ")
  404. if is_legacy:
  405. return f"""{matchobj['space']}const {matchobj['default_export']} = require({matchobj['path']});
  406. {matchobj['space']}const {new_object} = {matchobj['default_export']}"""
  407. new_object = f"""{{ [Symbol.for("default")]: {matchobj['default_export']},{new_object[1:]}"""
  408. return f"{matchobj['space']}const {new_object} = require({matchobj['path']})"
  409. return IMPORT_DEFAULT_AND_NAMED_RE.sub(repl, content)
  410. RELATIVE_REQUIRE_RE = re.compile(r"""
  411. ^[^/*\n]*require\((?P<quote>[\"'`])([^\"'`]+)(?P=quote)\) # require("some/path")
  412. """, re.MULTILINE | re.VERBOSE)
  413. def convert_relative_require(url, dependencies, content):
  414. """
  415. Convert the relative path contained in a 'require()'
  416. to the new path system (@module/path).
  417. Adds all modules path to dependencies.
  418. .. code-block:: javascript
  419. // Relative path:
  420. // before
  421. require("./path")
  422. // after
  423. require("@module/path")
  424. // Not a relative path:
  425. // before
  426. require("other_alias")
  427. // after
  428. require("other_alias")
  429. """
  430. new_content = content
  431. for quote, path in RELATIVE_REQUIRE_RE.findall(new_content):
  432. module_path = path
  433. if path.startswith(".") and "/" in path:
  434. pattern = rf"require\({quote}{path}{quote}\)"
  435. module_path = relative_path_to_module_path(url, path)
  436. repl = f'require("{module_path}")'
  437. new_content = re.sub(pattern, repl, new_content)
  438. dependencies.add(module_path)
  439. return new_content
  440. IMPORT_STAR = re.compile(r"""
  441. ^(?P<space>\s*) # indentation
  442. import\s+\*\s+as\s+ # import * as
  443. (?P<identifier>\w+) # alias
  444. \s*from\s* # from
  445. (?P<path>[^;\n]+) # path
  446. """, re.MULTILINE | re.VERBOSE)
  447. def convert_star_import(content):
  448. """
  449. Transpile import star.
  450. .. code-block:: javascript
  451. // before
  452. import * as name from "some/path"
  453. // after
  454. const name = require("some/path")
  455. """
  456. repl = r"\g<space>const \g<identifier> = require(\g<path>)"
  457. return IMPORT_STAR.sub(repl, content)
  458. IMPORT_DEFAULT_AND_STAR = re.compile(r"""
  459. ^(?P<space>\s*) # indentation
  460. import\s+ # import
  461. (?P<default_export>\w+)\s*,\s* # default export name,
  462. \*\s+as\s+ # * as
  463. (?P<named_exports_alias>\w+) # alias
  464. \s*from\s* # from
  465. (?P<path>[^;\n]+) # path
  466. """, re.MULTILINE | re.VERBOSE)
  467. def convert_default_and_star_import(content):
  468. """
  469. Transpile import star.
  470. .. code-block:: javascript
  471. // before
  472. import something, * as name from "some/path";
  473. // after
  474. const name = require("some/path");
  475. const something = name[Symbol.for("default")];
  476. """
  477. repl = r"""\g<space>const \g<named_exports_alias> = require(\g<path>);
  478. \g<space>const \g<default_export> = \g<named_exports_alias>[Symbol.for("default")]"""
  479. return IMPORT_DEFAULT_AND_STAR.sub(repl, content)
  480. IMPORT_UNNAMED_RELATIVE_RE = re.compile(r"""
  481. ^(?P<space>\s*) # indentation
  482. import\s+ # import
  483. (?P<path>[^;\n]+) # relative path
  484. """, re.MULTILINE | re.VERBOSE)
  485. def convert_unnamed_relative_import(content):
  486. """
  487. Transpile relative "direct" imports. Direct meaning they are not store in a variable.
  488. .. code-block:: javascript
  489. // before
  490. import "some/path"
  491. // after
  492. require("some/path")
  493. """
  494. repl = r"require(\g<path>)"
  495. return IMPORT_UNNAMED_RELATIVE_RE.sub(repl, content)
  496. URL_INDEX_RE = re.compile(r"""
  497. require\s* # require
  498. \(\s* # (
  499. (?P<path>(?P<quote>["'`])([^"'`]*/index/?)(?P=quote)) # path ended by /index or /index/
  500. \s*\) # )
  501. """, re.MULTILINE | re.VERBOSE)
  502. def remove_index(content):
  503. """
  504. Remove in the paths the /index.js.
  505. We want to be able to import a module just trough its directory name if it contains an index.js.
  506. So we no longer need to specify the index.js in the paths.
  507. """
  508. def repl(matchobj):
  509. path = matchobj["path"]
  510. new_path = path[: path.rfind("/index")] + path[0]
  511. return f"require({new_path})"
  512. return URL_INDEX_RE.sub(repl, content)
  513. def relative_path_to_module_path(url, path_rel):
  514. """Convert the relative path into a module path, which is more generic and
  515. fancy.
  516. :param str url:
  517. :param path_rel: a relative path to the current url.
  518. :return: module path (@module/...)
  519. """
  520. url_split = url.split("/")
  521. path_rel_split = path_rel.split("/")
  522. nb_back = len([v for v in path_rel_split if v == ".."]) + 1
  523. result = "/".join(url_split[:-nb_back] + [v for v in path_rel_split if not v in ["..", "."]])
  524. return url_to_module_path(result)
  525. ODOO_MODULE_RE = re.compile(r"""
  526. \s* # starting white space
  527. \/(\*|\/) # /* or //
  528. .* # any comment in between (optional)
  529. @odoo-module # '@odoo-module' statement
  530. (?P<ignore>\s+ignore)? # module in src | tests which should not be transpiled (optional)
  531. (\s+alias=(?P<alias>[^\s*]+))? # alias (e.g. alias=web.Widget, alias=@web/../tests/utils) (optional)
  532. (\s+default=(?P<default>\w+))? # no implicit default export (e.g. default=false) (optional)
  533. """, re.VERBOSE)
  534. def is_odoo_module(url, content):
  535. """
  536. Detect if the file is a native odoo module.
  537. We look for a comment containing @odoo-module.
  538. :param url:
  539. :param content: source code
  540. :return: is this a odoo module that need transpilation ?
  541. """
  542. result = ODOO_MODULE_RE.match(content)
  543. if result and result['ignore']:
  544. return False
  545. addon = url.split('/')[1]
  546. if url.startswith(f'/{addon}/static/src') or url.startswith(f'/{addon}/static/tests'):
  547. return True
  548. return bool(result)
  549. def get_aliased_odoo_define_content(module_path, content):
  550. """
  551. To allow smooth transition between the new system and the legacy one, we have the possibility to
  552. defined an alternative module name (an alias) that will act as proxy between legacy require calls and
  553. new modules.
  554. Example:
  555. If we have a require call somewhere in the odoo source base being:
  556. > vat AbstractAction require("web.AbstractAction")
  557. we have a problem when we will have converted to module to ES6: its new name will be more like
  558. "web/chrome/abstract_action". So the require would fail !
  559. So we add a second small modules, an alias, as such:
  560. > odoo.define("web/chrome/abstract_action", ['web.AbstractAction'], function (require) {
  561. > return require('web.AbstractAction')[Symbol.for("default")];
  562. > });
  563. To generate this, change your comment on the top of the file.
  564. .. code-block:: javascript
  565. // before
  566. /** @odoo-module */
  567. // after
  568. /** @odoo-module alias=web.AbstractAction */
  569. Notice that often, the legacy system acted like they it did defaukt imports. That's why we have the
  570. "[Symbol.for("default")];" bit. If your use case does not need this default import, just do:
  571. .. code-block:: javascript
  572. // before
  573. /** @odoo-module */
  574. // after
  575. /** @odoo-module alias=web.AbstractAction default=false */
  576. :return: the alias content to append to the source code.
  577. """
  578. matchobj = ODOO_MODULE_RE.match(content)
  579. if matchobj:
  580. alias = matchobj['alias']
  581. if alias:
  582. if matchobj['default']:
  583. return """\nodoo.define(`%s`, ['%s'], function (require) {
  584. return require('%s');
  585. });\n""" % (alias, module_path, module_path)
  586. else:
  587. return """\nodoo.define(`%s`, ['%s'], function (require) {
  588. return require('%s')[Symbol.for("default")];
  589. });\n""" % (alias, module_path, module_path)
  590. def convert_as(val):
  591. parts = val.split(" as ")
  592. return val if len(parts) < 2 else "%s: %s" % tuple(reversed(parts))
  593. def remove_as(val):
  594. parts = val.split(" as ")
  595. return val if len(parts) < 2 else parts[0]
上海开阖软件有限公司 沪ICP备12045867号-1