generate_properties.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. 'use strict';
  2. var fs = require('fs');
  3. var path = require('path');
  4. var babylon = require('babylon');
  5. var t = require('babel-types');
  6. var generate = require('babel-generator').default;
  7. var traverse = require('babel-traverse').default;
  8. var resolve = require('resolve');
  9. var camelToDashed = require('../lib/parsers').camelToDashed;
  10. var basename = path.basename;
  11. var dirname = path.dirname;
  12. var uniqueIndex = 0;
  13. function getUniqueIndex() {
  14. return uniqueIndex++;
  15. }
  16. var property_files = fs.readdirSync(path.resolve(__dirname, '../lib/properties')).filter(function (property) {
  17. return property.substr(-3) === '.js';
  18. });
  19. var out_file = fs.createWriteStream(path.resolve(__dirname, '../lib/properties.js'), {encoding: 'utf-8'});
  20. out_file.write('\'use strict\';\n\n// autogenerated\n\n');
  21. out_file.write('/*\n *\n * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSS2Properties\n */\n\n');
  22. function isModuleDotExports(node) {
  23. return (
  24. t.isMemberExpression(node, {computed: false}) &&
  25. t.isIdentifier(node.object, {name: 'module'}) &&
  26. t.isIdentifier(node.property, {name: 'exports'})
  27. );
  28. }
  29. function isRequire(node, filename) {
  30. if (
  31. t.isCallExpression(node) &&
  32. t.isIdentifier(node.callee, {name: 'require'}) &&
  33. node.arguments.length === 1 &&
  34. t.isStringLiteral(node.arguments[0])
  35. ) {
  36. var relative = node.arguments[0].value;
  37. var fullPath = resolve.sync(relative, {basedir: dirname(filename)});
  38. return {relative: relative, fullPath: fullPath};
  39. } else {
  40. return false;
  41. }
  42. }
  43. // step 1: parse all files and figure out their dependencies
  44. var parsedFilesByPath = {};
  45. property_files.map(function (property) {
  46. var filename = path.resolve(__dirname, '../lib/properties/' + property);
  47. var src = fs.readFileSync(filename, 'utf8');
  48. property = basename(property, '.js');
  49. var ast = babylon.parse(src);
  50. var dependencies = [];
  51. traverse(ast, {
  52. enter(path) {
  53. var r;
  54. if (r = isRequire(path.node, filename)) {
  55. dependencies.push(r.fullPath);
  56. }
  57. }
  58. });
  59. parsedFilesByPath[filename] = {
  60. filename: filename,
  61. property: property,
  62. ast: ast,
  63. dependencies: dependencies,
  64. };
  65. });
  66. // step 2: serialize the files in an order where dependencies are always above
  67. // the files they depend on
  68. var externalDependencies = [];
  69. var parsedFiles = [];
  70. var addedFiles = {};
  71. function addFile(filename, dependencyPath) {
  72. if (dependencyPath.indexOf(filename) !== -1) {
  73. throw new Error(
  74. 'Circular dependency: ' +
  75. dependencyPath.slice(dependencyPath.indexOf(filename)).concat([filename]).join(' -> ')
  76. );
  77. }
  78. var file = parsedFilesByPath[filename];
  79. if (addedFiles[filename]) {
  80. return;
  81. }
  82. if (!file) {
  83. externalDependencies.push(filename);
  84. } else {
  85. file.dependencies.forEach(function (dependency) {
  86. addFile(dependency, dependencyPath.concat([filename]));
  87. });
  88. parsedFiles.push(parsedFilesByPath[filename]);
  89. }
  90. addedFiles[filename] = true;
  91. }
  92. Object.keys(parsedFilesByPath).forEach(function (filename) {
  93. addFile(filename, []);
  94. });
  95. // Step 3: add files to output
  96. // renaming exports to local variables `moduleName_export_exportName`
  97. // and updating require calls as appropriate
  98. var moduleExportsByPath = {};
  99. var statements = [];
  100. externalDependencies.forEach(function (filename, i) {
  101. var id = t.identifier(
  102. 'external_dependency_' +
  103. basename(filename, '.js').replace(/[^A-Za-z]/g, '') +
  104. '_' + i
  105. );
  106. moduleExportsByPath[filename] = {defaultExports: id};
  107. var relativePath = path.relative(path.resolve(__dirname + '/../lib'), filename);
  108. if (relativePath[0] !== '.') {
  109. relativePath = './' + relativePath;
  110. }
  111. statements.push(t.variableDeclaration(
  112. 'var',
  113. [
  114. t.variableDeclarator(
  115. id,
  116. t.callExpression(
  117. t.identifier('require'),
  118. [
  119. t.stringLiteral(
  120. relativePath
  121. )
  122. ]
  123. )
  124. )
  125. ]
  126. ));
  127. });
  128. function getRequireValue(node, file) {
  129. var r;
  130. // replace require("./foo").bar with the named export from foo
  131. if (
  132. t.isMemberExpression(node, {computed: false}) &&
  133. (r = isRequire(node.object, file.filename))
  134. ) {
  135. var e = moduleExportsByPath[r.fullPath];
  136. if (!e) {
  137. return;
  138. }
  139. if (!e.namedExports) {
  140. return t.memberExpression(
  141. e.defaultExports,
  142. node.property
  143. );
  144. }
  145. if (!e.namedExports[node.property.name]) {
  146. throw new Error(r.relative + ' does not export ' + node.property.name);
  147. }
  148. return e.namedExports[node.property.name];
  149. // replace require("./foo") with the default export of foo
  150. } else if (r = isRequire(node, file.filename)) {
  151. var e = moduleExportsByPath[r.fullPath];
  152. if (!e) {
  153. if (/^\.\.\//.test(r.relative)) {
  154. node.arguments[0].value = r.relative.substr(1);
  155. }
  156. return;
  157. }
  158. return e.defaultExports;
  159. }
  160. }
  161. parsedFiles.forEach(function (file) {
  162. var namedExports = {};
  163. var localVariableMap = {};
  164. traverse(file.ast, {
  165. enter(path) {
  166. // replace require calls with the corresponding value
  167. var r;
  168. if (r = getRequireValue(path.node, file)) {
  169. path.replaceWith(r);
  170. return;
  171. }
  172. // if we see `var foo = require('bar')` we can just inline the variable
  173. // representing `require('bar')` wherever `foo` was used.
  174. if (
  175. t.isVariableDeclaration(path.node) &&
  176. path.node.declarations.length === 1 &&
  177. t.isIdentifier(path.node.declarations[0].id) &&
  178. (r = getRequireValue(path.node.declarations[0].init, file))
  179. ) {
  180. var newName = 'compiled_local_variable_reference_' + getUniqueIndex();
  181. path.scope.rename(
  182. path.node.declarations[0].id.name,
  183. newName
  184. );
  185. localVariableMap[newName] = r;
  186. path.remove();
  187. return;
  188. }
  189. // rename all top level variables to keep them local to the module
  190. if (
  191. t.isVariableDeclaration(path.node) &&
  192. t.isProgram(path.parent)
  193. ) {
  194. path.node.declarations.forEach(function (declaration) {
  195. path.scope.rename(
  196. declaration.id.name,
  197. file.property + '_local_var_' + declaration.id.name
  198. );
  199. });
  200. return;
  201. }
  202. // replace module.exports.bar with a variable for the named export
  203. if (
  204. t.isMemberExpression(path.node, {computed: false}) &&
  205. isModuleDotExports(path.node.object)
  206. ) {
  207. var name = path.node.property.name;
  208. var identifier = t.identifier(file.property + '_export_' + name);
  209. path.replaceWith(identifier);
  210. namedExports[name] = identifier;
  211. }
  212. }
  213. });
  214. traverse(file.ast, {
  215. enter(path) {
  216. if (
  217. t.isIdentifier(path.node) &&
  218. Object.prototype.hasOwnProperty.call(localVariableMap, path.node.name)
  219. ) {
  220. path.replaceWith(localVariableMap[path.node.name]);
  221. }
  222. }
  223. });
  224. var defaultExports = t.objectExpression(Object.keys(namedExports).map(function (name) {
  225. return t.objectProperty(t.identifier(name), namedExports[name]);
  226. }));
  227. moduleExportsByPath[file.filename] = {
  228. namedExports: namedExports,
  229. defaultExports: defaultExports
  230. };
  231. statements.push(t.variableDeclaration(
  232. 'var',
  233. Object.keys(namedExports).map(function (name) {
  234. return t.variableDeclarator(namedExports[name]);
  235. })
  236. ))
  237. statements.push.apply(statements, file.ast.program.body);
  238. });
  239. var propertyDefinitions = [];
  240. parsedFiles.forEach(function (file) {
  241. var dashed = camelToDashed(file.property);
  242. propertyDefinitions.push(
  243. t.objectProperty(
  244. t.identifier(file.property),
  245. t.identifier(file.property + '_export_definition')
  246. )
  247. );
  248. if (file.property !== dashed) {
  249. propertyDefinitions.push(
  250. t.objectProperty(
  251. t.stringLiteral(dashed),
  252. t.identifier(file.property + '_export_definition')
  253. )
  254. );
  255. }
  256. });
  257. var definePropertiesCall = t.callExpression(
  258. t.memberExpression(
  259. t.identifier('Object'),
  260. t.identifier('defineProperties')
  261. ),
  262. [
  263. t.identifier('prototype'),
  264. t.objectExpression(
  265. propertyDefinitions
  266. )
  267. ]
  268. );
  269. statements.push(t.expressionStatement(
  270. t.assignmentExpression(
  271. '=',
  272. t.memberExpression(
  273. t.identifier('module'),
  274. t.identifier('exports')
  275. ),
  276. t.functionExpression(
  277. null,
  278. [t.identifier('prototype')],
  279. t.blockStatement([t.expressionStatement(definePropertiesCall)])
  280. )
  281. )
  282. ));
  283. out_file.write(generate(t.program(statements)).code + '\n')
  284. out_file.end(function (err) {
  285. if (err) {
  286. throw err;
  287. }
  288. });