Last active
September 21, 2019 09:04
-
-
Save acutmore/64be16d030b763a0dcd39e23cf985302 to your computer and use it in GitHub Desktop.
code mod to update dependency injection annotations at YouView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference types="node" /> | |
import { mod } from "riceburn"; | |
import { TypescriptMod } from "riceburn/lib/interfaces"; | |
import ts = require("typescript"); | |
import cp = require("child_process"); | |
import fs = require("fs"); | |
const glob = require("glob"); | |
import { getServiceDescription } from "../phoenix/tools/webpack/hatch/typescript-dependency-annotations/tsc-dep-annotations"; | |
const { log } = console; | |
log("running SDL update"); | |
cp.execSync("git checkout './services/**/*.ts'"); | |
cp.execSync("git checkout './**/*.sdl'"); | |
function getSDL(node: ts.Node): SDL | void { | |
const { fileName } = node.getSourceFile(); | |
const sdlPath = fileName.replace(".ts", ".sdl").replace("/src", ""); | |
try { | |
return JSON.parse(fs.readFileSync(process.cwd() + "/" + sdlPath, "utf8")); | |
} catch (e) { | |
return void 0; | |
} | |
} | |
mod("./services/**/src/*.ts").asTypescript((node, modder) => { | |
if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node)) { | |
if (!node.modifiers) { | |
return; | |
} | |
if ("export default" !== node.modifiers.map(n => n.getText()).join(" ")) { | |
return; | |
} | |
const sdl = getSDL(node); | |
if (!sdl) { | |
return; | |
} | |
const { requires } = sdl; | |
if (requires === "auto") { | |
return; | |
} | |
const mods: TypescriptMod[] = []; | |
log(`Updating '${node.getSourceFile().fileName}'`); | |
const addImport = once(() => { | |
const firstStatement = node.getSourceFile().statements[0]; | |
mods.push( | |
modder.prepend( | |
firstStatement, | |
`import {Inject} from 'services/hatch';\n` | |
) | |
); | |
}); | |
const updateParam = (param: ts.ParameterDeclaration, index: number) => { | |
if (index >= requires.length) { | |
return; | |
} | |
if (param.initializer) { | |
return; | |
} | |
const newType = typeForSDLDependency( | |
requires[index], | |
param.type.getText().trim() | |
); | |
if (!newType) { | |
return; | |
} | |
if (newType.startsWith(`Inject.`)) { | |
addImport(); | |
} | |
mods.push(modder.replace(param.type, newType)); | |
}; | |
if (ts.isClassDeclaration(node)) { | |
mods.push(modder.prepend(node, "/**\n * @type {Hatch.Service}\n */\n")); | |
node.members.forEach(classElement => { | |
if (!ts.isConstructorDeclaration(classElement)) { | |
return; | |
} | |
classElement.parameters.forEach(updateParam); | |
}); | |
} | |
if (ts.isFunctionDeclaration(node)) { | |
mods.push(modder.prepend(node, "/**\n * @type {Hatch.Factory}\n */\n")); | |
node.parameters.forEach(updateParam); | |
} | |
return mods; | |
} | |
}); | |
log("reverse checking"); | |
glob.sync("./services/**/*.ts").forEach(async fileName => { | |
const sdlPath = fileName.replace(".ts", ".sdl").replace("/src", ""); | |
let updated = false; | |
let sdl: any; | |
try { | |
sdl = JSON.parse(fs.readFileSync(process.cwd() + "/" + sdlPath, "utf8")); | |
} catch (e) { | |
return void 0; | |
} | |
const inferredSDL = await getServiceDescription(fileName); | |
if (!inferredSDL) { | |
if (sdl.requires === "auto" || sdl.implements === "auto") { | |
throw new Error(); | |
} else { | |
return; // OK | |
} | |
} | |
if (sdl.requires !== "auto") { | |
if (equals(sdl.requires, inferredSDL.requires)) { | |
updated = true; | |
sdl.requires = "auto"; | |
} else { | |
// REVERT | |
log(`Reverting ${fileName} as 'requires' does not match`); | |
cp.execSync(`git checkout '${fileName}'`); | |
return; | |
} | |
} | |
if (sdl.implements !== "auto") { | |
log(sdl.implements); | |
log(inferredSDL.implements); | |
let sameInterfaces = true; | |
for (const iFace of sdl.implements) { | |
if (!inferredSDL.implements.includes(iFace)) { | |
sameInterfaces = false; | |
break; | |
} | |
} | |
if (sameInterfaces) { | |
updated = true; | |
sdl.implements = "auto"; | |
} | |
} | |
fs.writeFileSync(sdlPath, JSON.stringify(sdl, undefined, 4)); | |
}); | |
function equals(a, b) { | |
return JSON.stringify(a) === JSON.stringify(b); | |
} | |
function typeForSDLDependency( | |
dep: SDLDependency, | |
originalType: string | |
): string | void { | |
switch (dep.type) { | |
case "configuration": { | |
const explicitType = [ | |
"string", | |
"Array<string>", | |
"string[]", | |
"number", | |
"Array<number>", | |
"number[]", | |
"boolean", | |
"Array<boolean>", | |
"boolean[]" | |
].includes(originalType) | |
? "" | |
: ` & ${originalType}`; | |
return ( | |
`Inject.Config${dep.value | |
.split(".") | |
.map(v => `['${v}']`) | |
.join("")}` + explicitType | |
); | |
} | |
case "domElement": { | |
const explictType = | |
originalType === "HTMLElement" ? "" : `, ${originalType}`; | |
return `Inject.DOM<'${dep.value}'${explictType}>`; | |
} | |
case "interface": { | |
return void 0; // not a global type | |
} | |
case "library": { | |
if (/^youview\./.test(dep.value)) { | |
const explictType = ` & ${originalType}`; | |
return ( | |
`Inject.Zinc${dep.value | |
.replace("youview.", "") | |
.split(".") | |
.map(v => `['${v}']`) | |
.join("")}` + explictType | |
); | |
} else { | |
const parts = dep.value.split("."); | |
if (parts.length === 1 && parts[0] === "window") { | |
if (originalType === "any") { | |
return `Inject.Library<'window'> & any`; | |
} | |
return `Inject.Library<'window'>`; | |
} | |
if (parts[0] === "window") { | |
parts.splice(0, 1); | |
} | |
const explicitType = [ | |
"console", | |
"Date", | |
"document", | |
"fetch", | |
"localStorage", | |
"location", | |
"navigator", | |
"requestAnimationFrame", | |
"window" | |
].includes(parts[0]) | |
? "" | |
: `, ${originalType}`; | |
return `Inject.Library<'${parts[0]}'${explicitType}>${parts | |
.slice(1) | |
.map(v => `['${v}']`) | |
.join("")}`; | |
} | |
} | |
} | |
return void 0; | |
} | |
type Fun = (...args: any[]) => any; | |
function once<T extends Fun>(f: T): T { | |
let called = false; | |
let result = null; | |
return ((...args: any[]) => { | |
if (called) { | |
return result; | |
} | |
called = true; | |
result = f(...args); | |
return result; | |
}) as T; | |
} | |
interface SDLDependency { | |
type: string; | |
value: string; | |
} | |
interface SDL { | |
description?: string; | |
requires: SDLDependency[] | "auto"; | |
implements: string[]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"scripts": { | |
"start": "ts-node-dev --respawn --transpileOnly index.ts" | |
}, | |
"devDependencies": { | |
"@types/node": "^11.13.8", | |
"riceburn": "^1.2.1", | |
"ts-node-dev": "^1.0.0-pre.32", | |
"typescript": "^3.4.5" | |
} | |
} |
Author
acutmore
commented
Apr 30, 2019
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment