|
| 1 | +import type tslib from 'typescript/lib/tsserverlibrary' |
| 2 | +import requireFromString from 'require-from-string' |
| 3 | + |
| 4 | +declare const __TS_SEVER_PATH__: string | undefined |
| 5 | + |
| 6 | +const getPatchedNavModule = (ts: typeof tslib) => { |
| 7 | + const tsServerPath = typeof __TS_SEVER_PATH__ !== 'undefined' ? __TS_SEVER_PATH__ : require.main!.filename |
| 8 | + const mainScript = require('fs').readFileSync(tsServerPath, 'utf8') as string |
| 9 | + const startIdx = mainScript.indexOf('var NavigationBar;') |
| 10 | + const ph = '(ts.NavigationBar = {}));' |
| 11 | + const lines = mainScript.slice(startIdx, mainScript.indexOf(ph) + ph.length).split(/\r?\n/) |
| 12 | + const patchPlaces: { |
| 13 | + predicateString: string |
| 14 | + linesOffset: number |
| 15 | + addString?: string |
| 16 | + removeLines?: number |
| 17 | + }[] = [ |
| 18 | + { |
| 19 | + predicateString: 'function addChildrenRecursively(node)', |
| 20 | + linesOffset: 7, |
| 21 | + addString: ` |
| 22 | + case ts.SyntaxKind.JsxSelfClosingElement: |
| 23 | + addLeafNode(node) |
| 24 | + break; |
| 25 | + case ts.SyntaxKind.JsxElement: |
| 26 | + startNode(node) |
| 27 | + ts.forEachChild(node, addChildrenRecursively); |
| 28 | + endNode() |
| 29 | + break`, |
| 30 | + }, |
| 31 | + { |
| 32 | + predicateString: 'return "<unknown>";', |
| 33 | + linesOffset: -2, |
| 34 | + addString: ` |
| 35 | + case ts.SyntaxKind.JsxSelfClosingElement: |
| 36 | + return getNameFromJsxTag(node); |
| 37 | + case ts.SyntaxKind.JsxElement: |
| 38 | + return getNameFromJsxTag(node.openingElement);`, |
| 39 | + }, |
| 40 | + ] |
| 41 | + for (let { addString, linesOffset, predicateString, removeLines = 0 } of patchPlaces) { |
| 42 | + const addTypeIndex = lines.findIndex(line => line.includes(predicateString)) |
| 43 | + if (addTypeIndex !== -1) { |
| 44 | + lines.splice(addTypeIndex + linesOffset, removeLines, ...(addString ? [addString] : [])) |
| 45 | + } |
| 46 | + } |
| 47 | + const getModule = requireFromString('module.exports = (ts, getNameFromJsxTag) => {' + lines.join('\n') + 'return NavigationBar;}') |
| 48 | + const getNameFromJsxTag = (node: tslib.JsxSelfClosingElement | tslib.JsxOpeningElement) => { |
| 49 | + const { |
| 50 | + attributes: { properties }, |
| 51 | + } = node |
| 52 | + const tagName = node.tagName.getText() |
| 53 | + const addDotAttrs = ['class', 'className'] |
| 54 | + // TODO refactor to arr |
| 55 | + let idAdd = '' |
| 56 | + let classNameAdd = '' |
| 57 | + properties.forEach(attr => { |
| 58 | + if (!ts.isJsxAttribute(attr) || !attr.initializer) return |
| 59 | + const attrName = attr.name?.getText() |
| 60 | + if (!attrName) return |
| 61 | + if (addDotAttrs.includes(attrName)) { |
| 62 | + const textAdd = ts.isStringLiteral(attr.initializer) ? attr.initializer.text : '' |
| 63 | + for (let char of textAdd.split(' ')) { |
| 64 | + if (char) classNameAdd += `.${char}` |
| 65 | + } |
| 66 | + } else if (attrName === 'id' && ts.isStringLiteral(attr.initializer)) { |
| 67 | + idAdd = `#${attr.initializer.text}` |
| 68 | + } |
| 69 | + }) |
| 70 | + return tagName + classNameAdd + idAdd |
| 71 | + } |
| 72 | + return getModule(ts, getNameFromJsxTag) |
| 73 | +} |
| 74 | + |
| 75 | +let navModule |
| 76 | + |
| 77 | +export const getNavTreeItems = (ts: typeof tslib, info: tslib.server.PluginCreateInfo, fileName: string) => { |
| 78 | + if (!navModule) navModule = getPatchedNavModule(ts) |
| 79 | + const program = info.languageService.getProgram() |
| 80 | + if (!program) throw new Error('no program') |
| 81 | + const sourceFile = program?.getSourceFile(fileName) |
| 82 | + if (!sourceFile) throw new Error('no sourceFile') |
| 83 | + |
| 84 | + const cancellationToken = info.languageServiceHost.getCompilerHost?.()?.getCancellationToken?.() ?? { |
| 85 | + isCancellationRequested: () => false, |
| 86 | + throwIfCancellationRequested: () => {}, |
| 87 | + } |
| 88 | + return navModule.getNavigationTree(sourceFile, cancellationToken) |
| 89 | +} |
0 commit comments