Skip to content

Instantly share code, notes, and snippets.

@acutmore
Last active September 21, 2019 09:04
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 acutmore/64be16d030b763a0dcd39e23cf985302 to your computer and use it in GitHub Desktop.
Save acutmore/64be16d030b763a0dcd39e23cf985302 to your computer and use it in GitHub Desktop.
code mod to update dependency injection annotations at YouView
/// <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[];
}
{
"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"
}
}
@acutmore
Copy link
Author

cd GIT_REPOS
mkdir sdl-update
cd sdl-update
CLONE GIST
cd ../phoenix
node -r ts-node/register/transpile-only ../sdl-update/index.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment