From ff56a9896abd67dd5b06a69a4a6a296164444b63 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 30 Mar 2023 22:00:03 +0200 Subject: [PATCH] remove unused code --- .gitignore | 1 + src/compiler/client-script.ts | 21 -- .../client-script/find-do-somethings.ts | 68 ------- .../get-component-hash-from-scope.ts | 26 --- .../client-script/get-scoped-modules.ts | 180 ------------------ .../resolve-identifier-if-imported.ts | 55 ------ 6 files changed, 1 insertion(+), 350 deletions(-) delete mode 100644 src/compiler/client-script.ts delete mode 100644 src/compiler/client-script/find-do-somethings.ts delete mode 100644 src/compiler/client-script/get-component-hash-from-scope.ts delete mode 100644 src/compiler/client-script/get-scoped-modules.ts delete mode 100644 src/compiler/client-script/resolve-identifier-if-imported.ts diff --git a/.gitignore b/.gitignore index 2b50375..875907c 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,4 @@ package-lock.json .cc lib __generated +.unimportedrc.json diff --git a/src/compiler/client-script.ts b/src/compiler/client-script.ts deleted file mode 100644 index 8064aa0..0000000 --- a/src/compiler/client-script.ts +++ /dev/null @@ -1,21 +0,0 @@ -// extract "doSomething" functions from components and compile them into a file - -import Parser from './parser' -import findDoSomethings from './client-script/find-do-somethings' -import getScopedModules, {ClientModulesType} from './client-script/get-scoped-modules' -import {addMarker} from './profiler' - -export default function generateClientScriptTrees(parser: Parser): ClientModulesType { - /* - * todo: different approach: - * get stack from doSomething call, then extract function with (used) lexical scope - */ - // todo: make this whole wonky babel setup more robust to edge cases - // todo: faster (swc???) - addMarker('client-scripts', 'start') - const doSomethingsScopes = findDoSomethings(parser) - addMarker('client-scripts', 'find-do-somethings') - const result = getScopedModules(parser, doSomethingsScopes) - addMarker('client-scripts', 'end') - return result -} diff --git a/src/compiler/client-script/find-do-somethings.ts b/src/compiler/client-script/find-do-somethings.ts deleted file mode 100644 index f944b66..0000000 --- a/src/compiler/client-script/find-do-somethings.ts +++ /dev/null @@ -1,68 +0,0 @@ -import {CallExpression, Identifier, MemberExpression, Node} from '@babel/types' -import {NodePath, Scope} from '@babel/traverse' - -import {doSomething} from '#cherry-soda' -import Parser, {FileToImportsMapType} from '../parser' -import getAllScopeBindings from '../helpers/all-scope-bindings' -import resolveIdentifierIfImported from './resolve-identifier-if-imported' -import resolveImportFileSpecifier from '../helpers/resolve-import-file-specifier' -import {ensureArray} from '../../utils/array' -import getComponentHashFromScope from './get-component-hash-from-scope' - -export const cherrySodaIndex = resolveImportFileSpecifier('', '#cherry-soda') - -export type DoSomethingsScopesType = { [filename: string]: Map } - -export default function findDoSomethings(parser: Parser): DoSomethingsScopesType { - let doSomethingsScopes: DoSomethingsScopesType = {} - parser.fileNames.forEach(fileName => { - doSomethingsScopes[fileName] = new Map() - parser.traverseFunctionComponents(fileName, { - CallExpression(nodePath) { - const fileImports = parser.getImports(fileName) - if (isCherrySodaFunction(nodePath, fileImports, doSomething)) { - const thisScopeAndParents: Scope[] = [nodePath.scope] - getComponentHashFromScope(nodePath.scope, parser, fileName) - let scope = nodePath.scope - while (scope.parent) { - scope = scope.parent - thisScopeAndParents.push(scope) - } - doSomethingsScopes[fileName].set( - nodePath.node as CallExpression, - thisScopeAndParents.map(scope => scope.block as Node) - ) - } - }, - }) - }) - return doSomethingsScopes -} - -export function isCherrySodaFunction(callExpression: NodePath, - fileImports: FileToImportsMapType, - supposedFunctions: Function | string | (Function | string)[], - fullScope?: ReturnType) { - supposedFunctions = ensureArray(supposedFunctions) - supposedFunctions = supposedFunctions - .map(supposedFunction => typeof supposedFunction === 'function' ? supposedFunction.name : supposedFunction) - - // must be a named function - if (!['Identifier', 'MemberExpression'].includes(callExpression.node.callee.type)) return - fullScope ??= getAllScopeBindings(callExpression.scope) - // if variable -> resolve to source - const importedIdentifier = resolveIdentifierIfImported(callExpression.node.callee as Identifier | MemberExpression, fullScope) - if (!importedIdentifier) return false - const importedLocalName = importedIdentifier.name - // must be imported from cherry-soda - let importedName, importedFile - for (const importFilePath in fileImports) { - const importSpecifiers = fileImports[importFilePath] - if (importSpecifiers[importedLocalName]) { - importedName = importSpecifiers[importedLocalName] - importedFile = importFilePath - break - } - } - return supposedFunctions.includes(importedName) && importedFile === cherrySodaIndex -} diff --git a/src/compiler/client-script/get-component-hash-from-scope.ts b/src/compiler/client-script/get-component-hash-from-scope.ts deleted file mode 100644 index eefe265..0000000 --- a/src/compiler/client-script/get-component-hash-from-scope.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {Scope} from '@babel/traverse' - -import Parser from '../parser' -import {transformTsx} from '../../imports/jsx-patch-plugin' -import {numberToAlphanumeric} from '../../utils/number-to-string' -import {HashType} from '../../jsx/VirtualElement' - -const cache: { [filename: string]: Map } = {} - -export default function getComponentHashFromScope(scope: Scope, parser: Parser, fileName: string): HashType { - if (fileName in cache && cache[fileName].has(scope)) - return cache[fileName].get(scope) - const block = scope.block - const fileLines = parser.files[fileName].split("\n") - const componentLines = fileLines.slice(block.loc.start.line - 1, block.loc.end.line) - componentLines[0] = componentLines[0].slice(block.loc.start.column) - const lastLineIndex = componentLines.length - 1 - componentLines[lastLineIndex] = componentLines[lastLineIndex].slice(0, block.loc.end.column) - const functionString = componentLines.join("\n") - const hash = numberToAlphanumeric(Bun.hash(transformTsx(functionString).split("\n").slice(4).join("\n").trim()) as number) - if (!(fileName in cache)){ - cache[fileName] = new Map() - } - cache[fileName].set(scope, hash) - return hash -} diff --git a/src/compiler/client-script/get-scoped-modules.ts b/src/compiler/client-script/get-scoped-modules.ts deleted file mode 100644 index 3f883f2..0000000 --- a/src/compiler/client-script/get-scoped-modules.ts +++ /dev/null @@ -1,180 +0,0 @@ -import path from 'path' - -import { - assignmentExpression, - callExpression, - CallExpression, expressionStatement, - identifier, - Identifier, memberExpression, - Node, - numericLiteral, stringLiteral, variableDeclaration, - variableDeclarator -} from '@babel/types' -import {NodePath} from '@babel/traverse' -import babel, {BabelFileResult, transformFromAstSync} from '@babel/core' -import babelPluginMinifyDeadCodeElimination from 'babel-plugin-minify-dead-code-elimination' -import babelPluginRemoveUnusedImport from 'babel-plugin-remove-unused-import' - -import Parser from '../parser' -import {iterateObject} from '../../utils/iterate-object' -import {cherrySodaIndex, DoSomethingsScopesType, isCherrySodaFunction} from './find-do-somethings' -import resolveImportFileSpecifier from '../helpers/resolve-import-file-specifier' -import getAllScopeBindings from '../helpers/all-scope-bindings' -import {createRef, createState} from '#cherry-soda' -import {generateId} from '../../utils/random' -import {getState} from '../states-collector' -import getComponentHashFromScope from './get-component-hash-from-scope' -import {getClientState, registerStateChangeHandler} from '../../runtime' -import {addMarker} from '../profiler' - -export type ClientModulesType = { [filename: string]: BabelFileResult } - -export default function getScopedModules(parser: Parser, doSomethings: DoSomethingsScopesType): ClientModulesType { - const clientModules: ClientModulesType = {} - iterateObject(doSomethings, ([fileName, doSomethingScopeMap]) => { - const requisiteScopes = new Set( // deduplicate scopes - Array.from(doSomethingScopeMap.values()).flat() // all scopes in 1D array - ) - - function getNodeId(node: Node): Identifier | undefined { - const possibleProps = ['id', 'callee'] - for (const possibleProp of possibleProps) { - if (possibleProp in node && node[possibleProp].type === 'Identifier') return node[possibleProp] - } - } - - function nodeMatches(nodeA: Node, nodeB: Node) { - const nodeAId = getNodeId(nodeA) - const nodeBId = getNodeId(nodeB) - if (nodeAId && nodeBId) - return nodeAId.name === nodeBId.name - return null - } - - if (requisiteScopes.size === 0) // no doSomethings - return - const doSomethingCalls = Array.from(doSomethingScopeMap.keys()) - const functionComponents = Array.from(doSomethingScopeMap.values()).map(nodes => nodes[0]) - // console.log(functionComponents) - let programScopeBeforeImportDeletion - const probableStates = {} - // parser.printFileTree(fileName) - const stateDeclarationCalls: Map = new Map() - const getStateDeclarationCall = identifierNeedle => stateDeclarationCalls.get( - Array.from(stateDeclarationCalls.keys()).find(haystackIdentifier => identifierNeedle.name === haystackIdentifier.name) - ) - const ast = parser.traverseClonedFile(fileName, { - ImportDeclaration(nodePath) { - programScopeBeforeImportDeletion ??= getAllScopeBindings(nodePath.scope) - if (resolveImportFileSpecifier(path.dirname(fileName), nodePath.node.source.value) === cherrySodaIndex) - nodePath.remove() - }, - FunctionDeclaration(nodePath) { - if (functionComponents.some(functionComponent => nodeMatches(functionComponent, nodePath.node))) { - if (nodePath.parentPath.isExportDeclaration()) - nodePath.parentPath.replaceWith(nodePath) - nodePath.replaceWith(nodePath.get('body') /* always blockStatement */) - } - }, - CallExpression(nodePath) { - const fileImports = parser.getImports(fileName) - const allBindings = getAllScopeBindings(nodePath.scope) - const allBindingsBeforeImportDeletion = { - ...programScopeBeforeImportDeletion, - ...allBindings, - } - if ((doSomethingCalls as Node[]).some(node => nodeMatches(node, nodePath.node as Node))) { - const secondArgument = nodePath.node.arguments[1] - if (secondArgument && secondArgument.type === 'ArrayExpression') { - const arrayName = 'statesToListenTo_' + generateId() - nodePath.insertBefore(variableDeclaration('const', [variableDeclarator( - identifier(arrayName), - callExpression(identifier('Array'), [numericLiteral(secondArgument.elements.length)]) - )] - )) - secondArgument.elements - .forEach((stateIdentifier, i) => { - if (stateIdentifier.type !== 'Identifier' || !probableStates[stateIdentifier.name]) return - nodePath.insertBefore(expressionStatement( - assignmentExpression('=', - memberExpression(identifier(arrayName), numericLiteral(i), true), - stateIdentifier - ) - )) - const stateId = getState( - getComponentHashFromScope(nodePath.scope, parser, fileName), - (doSomethingCalls as Node[]).findIndex(node => nodeMatches(node, nodePath.node)), - i - ) as string - getStateDeclarationCall(stateIdentifier).arguments.unshift(stringLiteral(stateId)) - }) - nodePath.node.arguments[1] = identifier(arrayName) - } - getNodeId(nodePath.node).name = registerStateChangeHandler.name - } else if (isCherrySodaFunction(nodePath, fileImports, createState, allBindingsBeforeImportDeletion)) { - const identifier = getNodeId(nodePath.node) - identifier.name = getClientState.name - stateDeclarationCalls.set(getNodeId(nodePath.parentPath.node), nodePath.node) - } else if (isCherrySodaFunction(nodePath, fileImports, createRef, allBindingsBeforeImportDeletion)) { - const identifier = getNodeId(nodePath.node) - identifier.name = 'findNode' // todo: use function name from runtime - stateDeclarationCalls.set(getNodeId(nodePath.parentPath.node), nodePath.node) - } - }, - // @ts-ignore - 'AssignmentExpression|VariableDeclarator'(nodePath) { - const node = nodePath.node - const name = node.type === 'AssignmentExpression' ? node.left : node.id - const value = node.type === 'AssignmentExpression' ? node.right : node.init - if (value?.type !== 'CallExpression') return - const fakePath = { - scope: node.scope, - node: value - } - const fileImports = parser.getImports(fileName) - const allBindings = getAllScopeBindings(nodePath.scope) - const allBindingsBeforeImportDeletion = { - ...programScopeBeforeImportDeletion, - ...allBindings, - } - if (!isCherrySodaFunction(fakePath as NodePath, fileImports, [createState, createRef], allBindingsBeforeImportDeletion)) - return - probableStates[name.name] = value - }, - ReturnStatement(nodePath) { - if (!functionComponents.some(functionComponent => - nodeMatches(functionComponent, nodePath.scope.block as Node) - )) return - nodePath.remove() - }, - }) - addMarker('client-scripts', path.basename(fileName) + '-custom') - - const sourceCode = parser.files[fileName] - let result = transformFromAstSync(ast, sourceCode, { - ast: true, - sourceMaps: true, - sourceFileName: fileName, - plugins: [ - babel.createConfigItem(babelPluginMinifyDeadCodeElimination), - ], - }) - addMarker('client-scripts', path.basename(fileName) + '-eliminate-dead') - // because imports are placed at the top of the file, imported things that are later overwritten and - // subsequently not used are left out if both plugins were run in parallel - result = transformFromAstSync(result.ast, sourceCode, { - ast: true, - sourceMaps: true, - sourceFileName: fileName, - plugins: [ - babel.createConfigItem(babelPluginRemoveUnusedImport), - ], - }) - addMarker('client-scripts', path.basename(fileName) + '-unused-imports') - clientModules[fileName] = result - addMarker('client-scripts', path.basename(fileName)) - }) - addMarker('client-scripts', 'done') - - return clientModules -} diff --git a/src/compiler/client-script/resolve-identifier-if-imported.ts b/src/compiler/client-script/resolve-identifier-if-imported.ts deleted file mode 100644 index 372925e..0000000 --- a/src/compiler/client-script/resolve-identifier-if-imported.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {NodePath, Scope} from '@babel/traverse' -import { - VariableDeclarator, - Identifier, - MemberExpression, - ArrayExpression, - ObjectExpression, - NumericLiteral, - ObjectProperty -} from '@babel/types' - -import {messages as warnings, printWarning} from '../../messages/warnings' -import getAllScopeBindings from '../helpers/all-scope-bindings' - -export default function resolveIdentifierIfImported(expression: Identifier | MemberExpression, fullScope: Scope['bindings']) { - const expressionBinding = fullScope[ - expression.type === 'Identifier' - ? expression.name - : (expression.object as Identifier).name - ] - if (!expressionBinding) return false - if (expressionBinding.kind === 'module') return expression - else if (!['var', 'let', 'const'].includes(expressionBinding.kind)) return false - - const expressionProperty: NumericLiteral | Identifier = expression.type === 'MemberExpression' && expression.property as NumericLiteral | Identifier - const key = expressionProperty.type === 'NumericLiteral' ? expressionProperty.value : expressionProperty.name // todo: nested properties - const path: NodePath = expressionBinding.path as NodePath - const init: Identifier | ArrayExpression | ObjectExpression = path.node.init as Identifier | ArrayExpression | ObjectExpression - // todo: find key / property assignments and push them onto bindings - - let originalIdentifier - if (init.type == 'Identifier') { - originalIdentifier = init - } else if (init.type === 'ObjectExpression') { - const property: ObjectProperty = init.properties.find(property => - property.type === 'ObjectProperty' && - (property.key as Identifier).name === key - ) as ObjectProperty - if (!property) { - printWarning(warnings.compiler.backtrackCalleeToImport.couldNotFindKey, [key, (path.node.id as Identifier).name]) - return false - } - originalIdentifier = (property.value as Identifier) - } else if (init.type === 'ArrayExpression') { - const item: Identifier = init.elements[key] - if (!item) { - printWarning(warnings.compiler.backtrackCalleeToImport.couldNotFindKey, [key, (path.node.id as Identifier).name]) - return false - } - originalIdentifier = item - } else return false - - const originalIdentifierScope = getAllScopeBindings(expressionBinding.scope) - return resolveIdentifierIfImported(originalIdentifier, originalIdentifierScope) -}