generate_properties.js 8.7 KB

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