Skip to content

Instantly share code, notes, and snippets.

@tommyip
Created June 28, 2019 12:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tommyip/c113beb04295fd1225dabd3e789f0445 to your computer and use it in GitHub Desktop.
Save tommyip/c113beb04295fd1225dabd3e789f0445 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
1. Convert CommonJS to typescript module
1.1 Remove IIFE wrapping
1.2 Use ES6 style import
1.3 Use ES6 style export
1.4 Import from other modules
1.5 Remove from .eslintrc
1.6 Remove from js_typings
1.7 Remove from app.js bundle
1.8 Modifiy entry in test-js-with-node with .ts extension
1.9 Search codebase for mention of <module>.js
2. Add typings (use dts-gen?)
3. var -> let/const
4. Use typescript/es6 stuff
"""
import argparse
import os
import re
# from subprocess import call, check_output
from typing import Text, AnyStr
STATIC_JS_PATH = 'static/js/'
# Map name to module path
THIRD_PARTY_FUNCTION = {
'md5': 'blueimp-md5',
'ClipboardJS': 'clipboard',
'XDate': 'xdate',
'$': 'jquery',
'jQuery': 'jquery',
'toMarkdown': 'to-markdown',
}
THIRD_PARTY_NAMESPACE = {
'marked': '../third/marked/lib/marked.js',
'emoji_codes': '../generated/emoji/emoji_codes.js',
'pygments_data': '../generated/pygments_data.js',
'$': 'jquery',
'jQuery': 'jquery',
'_': 'underscore',
'Handlebars': 'handlebars/runtime',
'Sortable': 'sortablejs',
'WinChan': 'winchan',
}
def _remove_iife(ts_content: Text, module: Text) -> Text:
ts_content = ts_content.replace(f'var {module} = (function () {{\n\nvar exports = {{}};\n\n', '')
ts_content = re.sub(f'return exports;.+window\\.{module} = {module};\n', '', ts_content, flags=re.DOTALL)
return ts_content.strip() + '\n'
def _es6_imports(ts_content: Text) -> Text:
""" Check whether the module uses any third party libraries and import
them accordingly """
third_party_functions = THIRD_PARTY_FUNCTION.keys()
third_party_namespace = THIRD_PARTY_NAMESPACE.keys()
combined = {**THIRD_PARTY_FUNCTION, **THIRD_PARTY_NAMESPACE}
# Dict is already imported but with require
ts_content = ts_content.replace(
"var Dict = require('./dict').Dict;",
"import { Dict } from './dict';")
function_match = re.findall(rf'({"|".join(third_party_functions)})\(', ts_content)
namespace_match = re.findall(rf'({"|".join(third_party_namespace)})\.', ts_content)
unique_matches = list(set(function_match + namespace_match))
imports = []
if unique_matches:
for library in unique_matches:
imports.append(f"import {library} from '{combined[library]}';")
ts_content = '\n'.join(imports) + '\n' + ts_content
return ts_content
def _es6_exports(ts_content: Text) -> Text:
""" Use es6's exports when declaring public functions """
ts_content = re.sub(
'exports\\.([a-z_]+) = function (?:[a-z_]+)?\\((.*?)\\) {',
'export function \\g<1>(\\g<2>) {',
ts_content)
ts_content = re.sub('\n};', '\n}', ts_content)
ts_content = ts_content.replace('exports.', '')
return ts_content
def _require_from_other_js_modules(module: Text) -> None:
for filename in os.listdir(STATIC_JS_PATH):
if filename.endswith('.js'):
with open(STATIC_JS_PATH + filename) as f:
content = f.read()
if module + '.' in content:
require = f"var {module} = require('./{module}');"
# Check whether there are existing requires and insert at
# appropriate position by alphabetical order
lines = content.split('\n')
for insert_idx, line in enumerate(lines):
if 'require' not in line:
lines.insert(insert_idx, require)
if insert_idx == 0:
lines.insert(insert_idx + 1, '')
break
elif require < line:
lines.insert(insert_idx, require)
break
with open(STATIC_JS_PATH + filename, 'w') as f:
f.write('\n'.join(lines))
def _replace_in_file(pattern: AnyStr, replacement: AnyStr, filepath: str) -> None:
with open(filepath) as f:
content = f.read()
with open(filepath, 'w') as wf:
wf.write(re.sub(pattern, replacement, content))
def convert_module_system(module: Text) -> None:
js = STATIC_JS_PATH + module + '.js'
ts = STATIC_JS_PATH + module + '.ts'
with open(js) as f:
js_content = f.read()
ts_content = js_content
ts_content = _remove_iife(ts_content, module)
ts_content = _es6_imports(ts_content)
ts_content = _es6_exports(ts_content)
_require_from_other_js_modules(module)
_replace_in_file(f' +\"{module}\": false,\n', '', '.eslintrc.json')
_replace_in_file(f'declare var {module}: any;\n', '', STATIC_JS_PATH + 'js_typings/zulip/index.d.ts')
_replace_in_file(f'import \"js/{module}.js\";\n', '', STATIC_JS_PATH + 'bundles/app.js')
_replace_in_file(f'static/js/{module}\.js', f'static/js/{module}.ts', 'tools/test-js-with-node')
with open(ts, 'w') as f:
f.write(ts_content)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('module', type=str, help='JS module to be converted to TS')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--convert-module-system', action='store_true')
group.add_argument('--add-types', action='store_true')
args = parser.parse_args()
# Check this module exists
if not os.path.isfile(STATIC_JS_PATH + args.module + '.js'):
raise Exception('Cannot find module ' + args.module)
if args.convert_module_system:
convert_module_system(args.module)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment