diff --git a/package-lock.json b/package-lock.json index fb4cd37..51c4a60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@spec.dev/tables": "^0.0.18", "@spec.types/spec": "^0.0.27", "bn.js": "^5.2.1", + "comment-parser": "^1.3.1", "strip-comments": "^2.0.1" }, "devDependencies": { @@ -703,6 +704,14 @@ "node": ">= 10" } }, + "node_modules/comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/compare-versions": { "version": "3.6.0", "dev": true, @@ -3759,6 +3768,11 @@ "version": "7.2.0", "dev": true }, + "comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==" + }, "compare-versions": { "version": "3.6.0", "dev": true diff --git a/package.json b/package.json index cd510f7..6f1d43d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@spec.dev/tables": "^0.0.18", "@spec.types/spec": "^0.0.27", "bn.js": "^5.2.1", + "comment-parser": "^1.3.1", "strip-comments": "^2.0.1" }, "devDependencies": { diff --git a/src/lib/utils/file.ts b/src/lib/utils/file.ts index 09699a0..4998226 100644 --- a/src/lib/utils/file.ts +++ b/src/lib/utils/file.ts @@ -1,10 +1,20 @@ -import { StringKeyMap, Manifest, PropertyMetadata } from '../types' -import stripComments from 'strip-comments' +import { StringKeyMap, Manifest } from '../types' +import { promises as fs } from 'fs' +import { parse } from 'comment-parser' + +interface LineDictionary { + [key: string]: string +} + +interface Property { + name: string + type: string + desc: string +} export async function readTextFile(path: string): Promise { const decoder = new TextDecoder('utf-8') - // @ts-ignore - const data = await Deno.readFile(path) + const data = await fs.readFile(path) return decoder.decode(data) } @@ -19,39 +29,90 @@ export async function readManifest(liveObjectSpecPath: string): Promise { +export async function buildPropertyMetadata(liveObjectSpecPath: string): Promise { // Read Live Object spec.ts file contents. const specFileContents = await readTextFile(liveObjectSpecPath) - if (!specFileContents) return {} + if (!specFileContents) return [] // Get property lines of Live Object. - const propertyLines = findPropertyLines(specFileContents) - if (!propertyLines.length) return {} + const { propertyLines, comments, decoratorLines } = + findPropertyLinesAndComments(specFileContents) + if (!propertyLines.length) return [] + + // Get multiline comments + const parsed = parse(specFileContents) + + for (let i = 0; i < parsed.length; i++) { + const currentComment = parsed[i] + const sourceLength = currentComment.source.length + const lineNumber = currentComment.source[sourceLength - 1].number + 1 + const description = currentComment.description + comments[lineNumber] = description + } + + // Group and attribute comments to decorators + const descriptions = {} + for (const [decoratorLine, decoratorTitle] of Object.entries(decoratorLines)) { + let description = '' + let commentsToAdd: String[] = [] + for (const [lineNumber, comment] of Object.entries(comments)) { + const currentLineNumber = parseInt(lineNumber) + if (currentLineNumber < parseInt(decoratorLine)) { + commentsToAdd.push(comment) + delete comments[lineNumber] + } + } + description = commentsToAdd.join(' ') + descriptions[decoratorTitle] = description + } // Parse property name:type info from property lines. - const metadata = {} + let properties: Property[] = [] for (const line of propertyLines) { + // Get accomanying comment from property name:type + const desc = descriptions[line] const { name, type } = parsePropertyNameAndTypeFromLine(line) if (name && type) { - metadata[name] = { type } + let property = { name: name, type: type, desc: desc } + properties.push(property) } } - return metadata + return properties } function getLines(contents: string): string[] { return (contents || '').split('\n').map((line) => line.trim()) } -function findPropertyLines(contents: string): string[] { - const lines = getLines(stripComments(contents)) +function findPropertyLinesAndComments(contents: string): { + propertyLines: string[] + comments: LineDictionary + decoratorLines: LineDictionary +} { + const lines = getLines(contents) const propertyLines: string[] = [] + const comments: LineDictionary = {} + const decoratorLines: LineDictionary = {} let takeNextLine = false + for (let i = 0; i < lines.length; i++) { const line = lines[i] + // Avoid 0-indexed line number + const currentLineNumber = i + 1 + + // Designate lines to exclude for later + if (line.startsWith('class') && line.includes('extends')) { + decoratorLines[currentLineNumber] = 'Do not include.' + } + + // Found a comment line. + if (line.startsWith('//')) { + let cleanedLine = line.replace('//', '').trim() + comments[currentLineNumber] = cleanedLine + continue + } + // Found property decorator. if (line.startsWith('@Property(')) { takeNextLine = true @@ -64,11 +125,13 @@ function findPropertyLines(contents: string): string[] { // Take property line. if (takeNextLine) { + decoratorLines[currentLineNumber] = line propertyLines.push(line) takeNextLine = false } } - return propertyLines + + return { propertyLines, comments, decoratorLines } } function parsePropertyNameAndTypeFromLine(line: string): StringKeyMap {