From 3069314577d726523640910b39a40e4233be65a5 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Sat, 4 Jun 2022 11:10:08 -0400 Subject: [PATCH 1/3] Use uuid for helper lookups --- packages/api-docs/src/props/full-prop-type.ts | 39 ++++++++++++------- packages/api-docs/src/utility/prop-links.ts | 3 +- packages/api/package.json | 2 + packages/api/src/SymbolParser.ts | 27 +++++++++---- packages/api/src/types.ts | 10 +++++ .../__snapshots__/function-prop.test.ts.snap | 9 +++++ yarn.lock | 5 +++ 7 files changed, 73 insertions(+), 22 deletions(-) diff --git a/packages/api-docs/src/props/full-prop-type.ts b/packages/api-docs/src/props/full-prop-type.ts index ed15eb4..b1b663a 100644 --- a/packages/api-docs/src/props/full-prop-type.ts +++ b/packages/api-docs/src/props/full-prop-type.ts @@ -67,7 +67,8 @@ export class PropTypeNodes { public resolvedProp(prop: PropType): PropType { if (prop.parent) { - const parent = this.config.propLinks.getPropLink(prop.parent.name); + const key = prop.parent.token ? prop.parent.token : prop.parent.name; + const parent = this.config.propLinks.getPropLink(key); if (parent && isClassLikeProp(parent)) { const p = parent.properties?.find((p) => p.name === prop.name); if (p) { @@ -96,9 +97,10 @@ export class PropTypeNodes { return collapsibleNode(typeProp, name); } private isLinkedProp(prop: PropType): boolean { - const propName = typeof prop.type === 'string' ? prop.type : prop.name; - if (propName) { - const linkedProp = this.config.propLinks.getPropLink(propName); + const key = prop.token ? prop.token : prop.name; + + if (key) { + const linkedProp = this.config.propLinks.getPropLink(key); if (linkedProp && linkedProp !== prop) { return true; } @@ -107,17 +109,28 @@ export class PropTypeNodes { } private getType(prop: PropType): DocumentationNode[] { const propName = typeof prop.type === 'string' ? prop.type : prop.name; + const token = prop.token; + let linkedProp: PropType | undefined; + if (typeof propName === 'string') { - const linkedProp = this.config.propLinks.getPropLink(propName); - if (linkedProp) { - return [ - this.config.propLinks.propLink({ - name: propName, - loc: linkedProp.loc, - }), - ]; - } + linkedProp = this.config.propLinks.getPropLink(propName); } + + // Prefer token lookup + if (typeof token === 'string') { + linkedProp = this.config.propLinks.getPropLink(token); + } + + if (linkedProp && propName && token) { + return [ + this.config.propLinks.propLink({ + name: propName, + loc: linkedProp.loc, + token: linkedProp.token, + }), + ]; + } + return this.extractTypeNode(prop); } diff --git a/packages/api-docs/src/utility/prop-links.ts b/packages/api-docs/src/utility/prop-links.ts index 570f601..91a82aa 100644 --- a/packages/api-docs/src/utility/prop-links.ts +++ b/packages/api-docs/src/utility/prop-links.ts @@ -17,7 +17,8 @@ export class PropLinks { public propLink(prop: PropParent): DocumentationNode { const typeText = inlineCodeNode(prop.name); if (typeof prop.name === 'string') { - const link = this.getPropLink(prop.name); + const key = prop.token ? prop.token : prop.name; + const link = this.getPropLink(key); return linkNode( [typeText], link ? `#${link.name?.toLowerCase()}` : undefined, diff --git a/packages/api/package.json b/packages/api/package.json index ea3448c..26ae6aa 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -36,11 +36,13 @@ }, "license": "MIT", "dependencies": { + "uuid": "^8.3.2", "@structured-types/typescript-config": "^3.46.9", "deepmerge": "^4.2.2", "path-browserify": "^1.0.1" }, "devDependencies": { + "@types/uuid": "^8.3.4", "@types/path-browserify": "^1.0.0", "typescript": "^4.5.0" }, diff --git a/packages/api/src/SymbolParser.ts b/packages/api/src/SymbolParser.ts index b3a8c2a..f37b909 100644 --- a/packages/api/src/SymbolParser.ts +++ b/packages/api/src/SymbolParser.ts @@ -1,5 +1,6 @@ import * as ts from 'typescript'; import deepmerge from 'deepmerge'; +import { v4 as randomUUID } from 'uuid'; import { PropType, @@ -100,11 +101,10 @@ export class SymbolParser implements ISymbolParser { options: ParseOptions, ) { if (options.collectHelpers) { - if (!this._helpers[name]) { - const prop = { name }; - this._helpers[name] = prop; - return this.addRefSymbol(prop, symbol, true); - } + const token = randomUUID(); + const prop = { name, token }; + this._helpers[token] = prop; + return this.addRefSymbol(prop, symbol, true); } return undefined; } @@ -154,8 +154,16 @@ export class SymbolParser implements ISymbolParser { (typeof parentProp.type === 'string' ? parentProp.type : undefined); - const propParent: PropParent = { name }; - this.addParentSymbol(name, (parent as any).symbol, options); + let propParent: PropParent = { name }; + const prop = this.addParentSymbol( + name, + (parent as any).symbol, + options, + ); + if (prop) { + propParent = { name, token: prop.token }; + } + if (parentName !== name) { const loc = this.parseFilePath(options, false, parent); if (loc) { @@ -499,7 +507,10 @@ export class SymbolParser implements ISymbolParser { if (this.internalSymbol(symbol) !== undefined) { this.addRefSymbol({ name }, symbol, false); } else { - this.addParentSymbol(name, symbol, options); + const prop = this.addParentSymbol(name, symbol, options); + if (prop) { + p.token = prop.token!; + } } } }); diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index e79c8d7..7f99572 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -95,6 +95,11 @@ export interface PropParent { * the parent type name */ name: string; + + /** + * unique id for looking up helper + */ + token?: string; /** * optional source location. * will be available when collectSourceInfo option is set to true @@ -197,6 +202,11 @@ export interface PropType { * if collectParametersUsage option is set, this will collect parameters usage in function body */ usage?: { start: SourcePosition; end: SourcePosition }[]; + + /** + * unique id for looking up helpers + */ + token?: string; } /** diff --git a/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap b/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap index 79a256c..4126581 100644 --- a/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap +++ b/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap @@ -26,6 +26,7 @@ Object { "optional": true, "parent": Object { "name": "Props", + "token": "f3f2f08a-587d-47f0-a905-613c94dc79d6", }, }, Object { @@ -34,6 +35,7 @@ Object { "optional": true, "parent": Object { "name": "PropsWithChildren", + "token": "b2311635-2d2a-474e-9607-8e212ad22693", }, "properties": Array [ Object { @@ -114,6 +116,7 @@ Object { "extends": Array [ Object { "name": "ReactElement", + "token": "e47515cb-210c-418b-8682-c10b830f34a0", }, ], "kind": 14, @@ -140,6 +143,7 @@ Object { "name": "type", "parent": Object { "name": "ReactElement", + "token": "fc9257bd-9bc3-403a-a153-93112d896714", }, "type": "T", }, @@ -148,6 +152,7 @@ Object { "name": "props", "parent": Object { "name": "ReactElement", + "token": "5b3ae18f-6315-4f73-bfac-10c52bf45142", }, "type": "P", }, @@ -182,6 +187,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", + "token": "ba3bf90e-4a0d-40ca-bd13-584249218941", }, "properties": Array [ Object { @@ -204,6 +210,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", + "token": "f1c5edb2-3fd4-4ee5-9c29-fc1824946215", }, "properties": Array [ Object { @@ -226,6 +233,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", + "token": "78508f56-e532-449b-8ba2-ee608fa3e1d8", }, "properties": Array [ Object { @@ -248,6 +256,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", + "token": "f8e74da5-c56f-4fab-bbe0-a66d77ebb71d", }, "properties": Array [ Object { diff --git a/yarn.lock b/yarn.lock index cf0fb02..c7979e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4215,6 +4215,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" From 418d5b60dab86b019d4e9db90f59d4e323707f44 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Mon, 6 Jun 2022 10:13:32 -0400 Subject: [PATCH 2/3] fix tests --- packages/api/package.json | 4 +- packages/api/src/SymbolParser.ts | 166 +++++++++++------- .../test/typescript/class/class-prop.test.ts | 6 +- .../test/typescript/enum/enum-prop.test.ts | 4 + .../__snapshots__/function-prop.test.ts.snap | 18 +- .../typescript/function/function-prop.test.ts | 25 ++- .../interface/interface-prop.test.ts | 30 +++- .../typescript/object/object-prop.test.ts | 7 + .../test/typescript/type/type-prop.test.ts | 25 ++- yarn.lock | 10 +- 10 files changed, 194 insertions(+), 101 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 26ae6aa..cc38f55 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -36,13 +36,13 @@ }, "license": "MIT", "dependencies": { - "uuid": "^8.3.2", + "object-hash": "^3.0.0", "@structured-types/typescript-config": "^3.46.9", "deepmerge": "^4.2.2", "path-browserify": "^1.0.1" }, "devDependencies": { - "@types/uuid": "^8.3.4", + "@types/object-hash": "^2.2.1", "@types/path-browserify": "^1.0.0", "typescript": "^4.5.0" }, diff --git a/packages/api/src/SymbolParser.ts b/packages/api/src/SymbolParser.ts index f37b909..f10b273 100644 --- a/packages/api/src/SymbolParser.ts +++ b/packages/api/src/SymbolParser.ts @@ -1,6 +1,6 @@ import * as ts from 'typescript'; import deepmerge from 'deepmerge'; -import { v4 as randomUUID } from 'uuid'; +import hash from 'object-hash'; import { PropType, @@ -97,13 +97,14 @@ export class SymbolParser implements ISymbolParser { private addParentSymbol( name: string, + node: ts.Node, symbol: ts.Symbol, options: ParseOptions, ) { if (options.collectHelpers) { - const token = randomUUID(); - const prop = { name, token }; - this._helpers[token] = prop; + const id = this.getId(node, name, options); + const prop = { name, token: id }; + this._helpers[id] = prop; return this.addRefSymbol(prop, symbol, true); } return undefined; @@ -114,10 +115,17 @@ export class SymbolParser implements ISymbolParser { symbol: ts.Symbol, options: ParseOptions, ) { - return ( - this.addParentSymbol(name, symbol, options) || - this.addRefSymbol({ name }, symbol, false) - ); + const declaration = getSymbolDeclaration(symbol); + + if (declaration) { + const prop = this.addParentSymbol(name, declaration, symbol, options); + + if (prop) { + return prop; + } + } + + return this.addRefSymbol({ name }, symbol, false); } private getParent( node: ts.Node, @@ -157,9 +165,11 @@ export class SymbolParser implements ISymbolParser { let propParent: PropParent = { name }; const prop = this.addParentSymbol( name, + parent, (parent as any).symbol, options, ); + if (prop) { propParent = { name, token: prop.token }; } @@ -207,6 +217,7 @@ export class SymbolParser implements ISymbolParser { }, }; } + private parseFilePath = ( options: ParseOptions, isTopLevel: boolean, @@ -218,71 +229,91 @@ export class SymbolParser implements ISymbolParser { node && (isTopLevel || options.collectInnerLocations) ) { - const typeNode = - ts.isVariableDeclaration(node) && - node.initializer && - ts.isIdentifier(node.initializer) - ? getSymbolDeclaration(this.getSymbolAtLocation(node.initializer)) - : node; - if (typeNode) { - const source = typeNode.getSourceFile(); - if (!location) { - location = {}; - } - location.filePath = source.fileName; + location = this.getLocation(node, options); + } + return location; + }; + + private getId(node: ts.Node, fallback: string, options: ParseOptions) { + const location = this.getLocation(node, options); + + if (location && location.filePath && location.loc) { + const digest = hash({ + filePath: location.filePath, + start: location.loc.start, + end: location.loc.end, + }); + + return `${fallback}:${digest.slice(0, 10)}`; + } + + return fallback; + } - if (options.collectSourceInfo === 'body') { - const fn = getInitializer(typeNode) || typeNode; + private getLocation(node: ts.Node, options: ParseOptions) { + let location: SourceLocation | undefined = undefined; + const typeNode = + ts.isVariableDeclaration(node) && + node.initializer && + ts.isIdentifier(node.initializer) + ? getSymbolDeclaration(this.getSymbolAtLocation(node.initializer)) + : node; + if (typeNode) { + const source = typeNode.getSourceFile(); + if (!location) { + location = {}; + } + location.filePath = source.fileName; + + if (options.collectSourceInfo === 'body') { + const fn = getInitializer(typeNode) || typeNode; + if ( + ts.isArrowFunction(fn) || + ts.isFunctionExpression(fn) || + ts.isGetAccessorDeclaration(fn) || + ts.isConstructorDeclaration(fn) || + ts.isMethodDeclaration(fn) || + ts.isFunctionDeclaration(fn) + ) { + let startPost = fn.parameters.pos; + let start = source.getLineAndCharacterOfPosition(startPost); + const newLineChar = + ts.getDefaultFormatCodeSettings().newLineCharacter || /\r?\n/; + const line = fn.getSourceFile().text.split(newLineChar)[start.line]; if ( - ts.isArrowFunction(fn) || - ts.isFunctionExpression(fn) || - ts.isGetAccessorDeclaration(fn) || - ts.isConstructorDeclaration(fn) || - ts.isMethodDeclaration(fn) || - ts.isFunctionDeclaration(fn) + start.character > 0 && + (line[start.character - 1] === '(' || + line[start.character - 1] === ' ') ) { - let startPost = fn.parameters.pos; - let start = source.getLineAndCharacterOfPosition(startPost); - const newLineChar = - ts.getDefaultFormatCodeSettings().newLineCharacter || /\r?\n/; - const line = fn.getSourceFile().text.split(newLineChar)[start.line]; - if ( - start.character > 0 && - (line[start.character - 1] === '(' || - line[start.character - 1] === ' ') - ) { - startPost -= 1; - start = source.getLineAndCharacterOfPosition(startPost); - } - while ( - start.character < line.length && - line[start.character] === ' ' - ) { - startPost += 1; - start = source.getLineAndCharacterOfPosition(startPost); - } - const end = source.getLineAndCharacterOfPosition( - (fn.body || getInitializer(fn) || fn).getEnd(), - ); - - location.loc = this.adjustLocation(start, end); - return location; + startPost -= 1; + start = source.getLineAndCharacterOfPosition(startPost); } - } - const nameNode = - ts.getNameOfDeclaration(typeNode as ts.Declaration) || typeNode; - if (nameNode) { - const start = source.getLineAndCharacterOfPosition( - nameNode.getStart(), + while ( + start.character < line.length && + line[start.character] === ' ' + ) { + startPost += 1; + start = source.getLineAndCharacterOfPosition(startPost); + } + const end = source.getLineAndCharacterOfPosition( + (fn.body || getInitializer(fn) || fn).getEnd(), ); - const end = source.getLineAndCharacterOfPosition(nameNode.getEnd()); + location.loc = this.adjustLocation(start, end); return location; } } + const nameNode = + ts.getNameOfDeclaration(typeNode as ts.Declaration) || typeNode; + if (nameNode) { + const start = source.getLineAndCharacterOfPosition(nameNode.getStart()); + const end = source.getLineAndCharacterOfPosition(nameNode.getEnd()); + location.loc = this.adjustLocation(start, end); + return location; + } } - return location; - }; + } + public parseProperties( properties: ts.NodeArray< | ts.ClassElement @@ -507,9 +538,12 @@ export class SymbolParser implements ISymbolParser { if (this.internalSymbol(symbol) !== undefined) { this.addRefSymbol({ name }, symbol, false); } else { - const prop = this.addParentSymbol(name, symbol, options); - if (prop) { - p.token = prop.token!; + const node = getSymbolDeclaration(symbol); + if (node) { + const prop = this.addParentSymbol(name, node, symbol, options); + if (prop) { + p.token = prop.token!; + } } } } diff --git a/packages/api/test/typescript/class/class-prop.test.ts b/packages/api/test/typescript/class/class-prop.test.ts index facde24..5f98540 100644 --- a/packages/api/test/typescript/class/class-prop.test.ts +++ b/packages/api/test/typescript/class/class-prop.test.ts @@ -81,10 +81,10 @@ describe('class', () => { Bar: { name: 'Bar', kind: 13, - extends: [{ name: 'Foo' }], + extends: [{ name: 'Foo', token: 'Foo:0be18c18e8' }], }, __helpers: { - Foo: { + 'Foo:0be18c18e8': { name: 'Foo', kind: 13, properties: [ @@ -96,6 +96,7 @@ describe('class', () => { value: false, }, ], + token: 'Foo:0be18c18e8', }, }, }); @@ -126,6 +127,7 @@ describe('class', () => { { parent: { name: 'Generic', + token: 'Generic:c82f927d27', }, name: 'value', kind: 3, diff --git a/packages/api/test/typescript/enum/enum-prop.test.ts b/packages/api/test/typescript/enum/enum-prop.test.ts index 324a901..ceeccdb 100644 --- a/packages/api/test/typescript/enum/enum-prop.test.ts +++ b/packages/api/test/typescript/enum/enum-prop.test.ts @@ -19,6 +19,7 @@ describe('enum-prop', () => { name: 'Warning', parent: { name: 'DiagnosticCategory', + token: 'DiagnosticCategory:08d2ee6b9a', }, kind: 2, value: 0, @@ -27,6 +28,7 @@ describe('enum-prop', () => { name: 'Error', parent: { name: 'DiagnosticCategory', + token: 'DiagnosticCategory:08d2ee6b9a', }, kind: 2, value: 1, @@ -35,6 +37,7 @@ describe('enum-prop', () => { name: 'Suggestion', parent: { name: 'DiagnosticCategory', + token: 'DiagnosticCategory:08d2ee6b9a', }, kind: 2, value: 2, @@ -43,6 +46,7 @@ describe('enum-prop', () => { name: 'Message', parent: { name: 'DiagnosticCategory', + token: 'DiagnosticCategory:08d2ee6b9a', }, kind: 2, value: 3, diff --git a/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap b/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap index 4126581..5f8f000 100644 --- a/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap +++ b/packages/api/test/typescript/function/__snapshots__/function-prop.test.ts.snap @@ -26,7 +26,7 @@ Object { "optional": true, "parent": Object { "name": "Props", - "token": "f3f2f08a-587d-47f0-a905-613c94dc79d6", + "token": "Props:1ec05c5ed1", }, }, Object { @@ -35,7 +35,7 @@ Object { "optional": true, "parent": Object { "name": "PropsWithChildren", - "token": "b2311635-2d2a-474e-9607-8e212ad22693", + "token": "PropsWithChildren:129c74d001", }, "properties": Array [ Object { @@ -116,7 +116,7 @@ Object { "extends": Array [ Object { "name": "ReactElement", - "token": "e47515cb-210c-418b-8682-c10b830f34a0", + "token": "ReactElement:b127325d7e", }, ], "kind": 14, @@ -143,7 +143,7 @@ Object { "name": "type", "parent": Object { "name": "ReactElement", - "token": "fc9257bd-9bc3-403a-a153-93112d896714", + "token": "ReactElement:b127325d7e", }, "type": "T", }, @@ -152,7 +152,7 @@ Object { "name": "props", "parent": Object { "name": "ReactElement", - "token": "5b3ae18f-6315-4f73-bfac-10c52bf45142", + "token": "ReactElement:b127325d7e", }, "type": "P", }, @@ -187,7 +187,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", - "token": "ba3bf90e-4a0d-40ca-bd13-584249218941", + "token": "FunctionComponent:63e780f521", }, "properties": Array [ Object { @@ -210,7 +210,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", - "token": "f1c5edb2-3fd4-4ee5-9c29-fc1824946215", + "token": "FunctionComponent:63e780f521", }, "properties": Array [ Object { @@ -233,7 +233,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", - "token": "78508f56-e532-449b-8ba2-ee608fa3e1d8", + "token": "FunctionComponent:63e780f521", }, "properties": Array [ Object { @@ -256,7 +256,7 @@ Object { "optional": true, "parent": Object { "name": "FunctionComponent", - "token": "f8e74da5-c56f-4fab-bbe0-a66d77ebb71d", + "token": "FunctionComponent:63e780f521", }, "properties": Array [ Object { diff --git a/packages/api/test/typescript/function/function-prop.test.ts b/packages/api/test/typescript/function/function-prop.test.ts index 8adf3ef..ee21898 100644 --- a/packages/api/test/typescript/function/function-prop.test.ts +++ b/packages/api/test/typescript/function/function-prop.test.ts @@ -83,6 +83,7 @@ describe('function', () => { name: 'm', parent: { name: 'T', + token: 'T:27b151585c', }, kind: 1, description: 'base type member property', @@ -99,7 +100,7 @@ describe('function', () => { returns: { kind: 14, type: 'Bear', - extends: [{ name: 'Internal' }], + extends: [{ name: 'Internal', token: 'Internal:1551f01e82' }], properties: [ { name: 'honey', @@ -110,6 +111,7 @@ describe('function', () => { name: 'm', parent: { name: 'Internal', + token: 'Internal:1551f01e82', }, kind: 1, description: 'string type member', @@ -121,11 +123,11 @@ describe('function', () => { description: 'exported function', }, __helpers: { - Bear: { + 'Bear:e62a2f1037': { description: 'interface extending another one', kind: 14, name: 'Bear', - extends: [{ name: 'Internal' }], + extends: [{ name: 'Internal', token: 'Internal:1551f01e82' }], properties: [ { description: 'boolean type member', @@ -138,11 +140,13 @@ describe('function', () => { name: 'm', parent: { name: 'Internal', + token: 'Internal:1551f01e82', }, }, ], + token: 'Bear:e62a2f1037', }, - ExtendT: { + 'ExtendT:3552cd3c86': { description: 'extended type', kind: 15, name: 'ExtendT', @@ -153,6 +157,7 @@ describe('function', () => { name: 'm', parent: { name: 'T', + token: 'T:27b151585c', }, }, { @@ -161,9 +166,10 @@ describe('function', () => { name: 'honey', }, ], + token: 'ExtendT:3552cd3c86', }, - T: { + 'T:27b151585c': { name: 'T', kind: 15, properties: [ @@ -174,9 +180,10 @@ describe('function', () => { }, ], description: 'base type', + token: 'T:27b151585c', }, - Internal: { + 'Internal:1551f01e82': { name: 'Internal', kind: 14, properties: [ @@ -187,6 +194,7 @@ describe('function', () => { }, ], description: 'internal interface with one member', + token: 'Internal:1551f01e82', }, }, }); @@ -242,6 +250,7 @@ describe('function', () => { { parent: { name: 'Foo', + token: 'Foo:17cc78eebc', }, static: true, readonly: true, @@ -302,6 +311,7 @@ describe('function', () => { { parent: { name: 'GenericInterface', + token: 'GenericInterface:236d9b3683', }, name: 'm', type: 'T', @@ -336,7 +346,7 @@ describe('function', () => { ], }, __helpers: { - GenericInterface: { + 'GenericInterface:236d9b3683': { name: 'GenericInterface', kind: 14, generics: [ @@ -350,6 +360,7 @@ describe('function', () => { type: 'T', }, ], + token: 'GenericInterface:236d9b3683', }, }, }); diff --git a/packages/api/test/typescript/interface/interface-prop.test.ts b/packages/api/test/typescript/interface/interface-prop.test.ts index d5ce68c..e4f2113 100644 --- a/packages/api/test/typescript/interface/interface-prop.test.ts +++ b/packages/api/test/typescript/interface/interface-prop.test.ts @@ -113,7 +113,7 @@ describe('interface', () => { Props: { name: 'Props', kind: 14, - extends: [{ name: 'Base' }], + extends: [{ name: 'Base', token: 'Base:c2d43652d1' }], properties: [ { name: 'm', @@ -125,6 +125,7 @@ describe('interface', () => { name: 'n', parent: { name: 'Base', + token: 'Base:c2d43652d1', }, optional: true, kind: 15, @@ -220,7 +221,10 @@ describe('interface', () => { Bear: { name: 'Bear', kind: 14, - extends: [{ name: 'Internal' }, { name: 'Home' }], + extends: [ + { name: 'Internal', token: 'Internal:9ba9eeb224' }, + { name: 'Home', token: 'Home:4be6b0aae9' }, + ], properties: [ { kind: 3, @@ -230,6 +234,7 @@ describe('interface', () => { { parent: { name: 'Internal', + token: 'Internal:9ba9eeb224', }, kind: 1, name: 'm', @@ -238,7 +243,18 @@ describe('interface', () => { { parent: { name: 'Home', + token: 'Home:4be6b0aae9', }, + properties: [ + { + kind: 1, + name: 'name', + }, + { + kind: 2, + name: 'age', + }, + ], name: 'resident', kind: 15, }, @@ -246,7 +262,7 @@ describe('interface', () => { description: 'interface extending another one', }, __helpers: { - Internal: { + 'Internal:9ba9eeb224': { name: 'Internal', kind: 14, properties: [ @@ -257,8 +273,9 @@ describe('interface', () => { }, ], description: 'internal interface with one member', + token: 'Internal:9ba9eeb224', }, - Home: { + 'Home:4be6b0aae9': { name: 'Home', kind: 14, properties: [ @@ -277,6 +294,7 @@ describe('interface', () => { ], }, ], + token: 'Home:4be6b0aae9', }, }, }); @@ -296,6 +314,7 @@ describe('interface', () => { description: 'kind is an enum constant', parent: { name: 'StringEnums', + token: 'StringEnums:fb0af3ee11', }, value: 'UP', type: 'Up', @@ -308,7 +327,7 @@ describe('interface', () => { ], }, __helpers: { - StringEnums: { + 'StringEnums:fb0af3ee11': { name: 'StringEnums', kind: 5, properties: [ @@ -318,6 +337,7 @@ describe('interface', () => { value: 'UP', }, ], + token: 'StringEnums:fb0af3ee11', }, }, }); diff --git a/packages/api/test/typescript/object/object-prop.test.ts b/packages/api/test/typescript/object/object-prop.test.ts index 05e9696..1f17a57 100644 --- a/packages/api/test/typescript/object/object-prop.test.ts +++ b/packages/api/test/typescript/object/object-prop.test.ts @@ -72,6 +72,7 @@ describe('object', () => { name: 'myprop', parent: { name: 'ShorthandObj', + token: 'ShorthandObj:5dbeebf9f0', }, kind: 26, alias: 'shorthand', @@ -80,6 +81,7 @@ describe('object', () => { name: 'name', parent: { name: 'ShorthandObj', + token: 'ShorthandObj:5dbeebf9f0', }, kind: 1, value: 'name', @@ -89,6 +91,7 @@ describe('object', () => { name: 'address', parent: { name: 'ShorthandObj', + token: 'ShorthandObj:5dbeebf9f0', }, kind: 1, value: '1022 Glover str', @@ -113,6 +116,7 @@ describe('object', () => { value: 'name', parent: { name: 'ShorthandObj', + token: 'ShorthandObj:5dbeebf9f0', }, }, { @@ -121,6 +125,7 @@ describe('object', () => { value: '1022 Glover str', parent: { name: 'ShorthandObj', + token: 'ShorthandObj:5dbeebf9f0', }, }, ], @@ -144,6 +149,7 @@ describe('object', () => { name: 'url', parent: { name: 'Site', + token: 'Site:6be9a4ea59', }, kind: 1, value: 'https://google.com', @@ -159,6 +165,7 @@ describe('object', () => { name: 'url', parent: { name: 'Site', + token: 'Site:6be9a4ea59', }, kind: 1, value: 'https://facebook.com', diff --git a/packages/api/test/typescript/type/type-prop.test.ts b/packages/api/test/typescript/type/type-prop.test.ts index d33f1e0..4653cbb 100644 --- a/packages/api/test/typescript/type/type-prop.test.ts +++ b/packages/api/test/typescript/type/type-prop.test.ts @@ -36,6 +36,7 @@ describe('type', () => { name: 'id', parent: { name: 'Props', + token: 'Props:992e576be5', }, kind: 1, }, @@ -43,6 +44,7 @@ describe('type', () => { name: 'bool', parent: { name: 'Props', + token: 'Props:992e576be5', }, kind: 3, }, @@ -50,6 +52,7 @@ describe('type', () => { name: 'children', parent: { name: 'PropsWithChildren', + token: 'PropsWithChildren:0fb2bba6dd', }, optional: true, kind: 1, @@ -72,6 +75,7 @@ describe('type', () => { type: 'Type', parent: { name: 'GenericArrayType', + token: 'GenericArrayType:f4ef3cb9d4', }, }, ], @@ -84,7 +88,7 @@ describe('type', () => { ], }, __helpers: { - GenericArrayType: { + 'GenericArrayType:f4ef3cb9d4': { description: 'generic interface', name: 'GenericArrayType', kind: 15, @@ -100,6 +104,7 @@ describe('type', () => { type: 'Type', }, ], + token: 'GenericArrayType:f4ef3cb9d4', }, }, }); @@ -178,6 +183,7 @@ describe('type', () => { name: 'stringProp', parent: { name: 'MainType', + token: 'MainType:672e8ca0ba', }, kind: 1, description: 'string prop description', @@ -213,6 +219,7 @@ describe('type', () => { name: 'children', parent: { name: 'Parent', + token: 'Parent:7049d5a61a', }, }, ], @@ -298,6 +305,7 @@ describe('type', () => { kind: 1, parent: { name: 'T', + token: 'T:879fabfe9e', }, }, { @@ -309,7 +317,7 @@ describe('type', () => { name: 'ExtendT', }, __helpers: { - T: { + 'T:879fabfe9e': { description: 'base type', name: 'T', kind: 15, @@ -320,6 +328,7 @@ describe('type', () => { kind: 1, }, ], + token: 'T:879fabfe9e', }, }, }); @@ -427,13 +436,14 @@ describe('type', () => { type: 'Type', parent: { name: 'GenericInterface', + token: 'GenericInterface:0d09a4c291', }, }, ], name: 'GenericConsumer', }, __helpers: { - GenericInterface: { + 'GenericInterface:0d09a4c291': { description: 'upstream interface', name: 'GenericInterface', kind: 14, @@ -449,6 +459,7 @@ describe('type', () => { type: 'Type', }, ], + token: 'GenericInterface:0d09a4c291', }, }, }); @@ -468,6 +479,7 @@ describe('type', () => { kind: 1, parent: { name: 'A', + token: 'A:7411e131cb', }, }, { @@ -475,13 +487,14 @@ describe('type', () => { kind: 2, parent: { name: 'B', + token: 'B:f95c97ee09', }, }, ], name: 'Intersect', }, __helpers: { - A: { + 'A:7411e131cb': { description: 'type A', name: 'A', kind: 15, @@ -491,8 +504,9 @@ describe('type', () => { kind: 1, }, ], + token: 'A:7411e131cb', }, - B: { + 'B:f95c97ee09': { description: 'type B', name: 'B', kind: 15, @@ -502,6 +516,7 @@ describe('type', () => { kind: 2, }, ], + token: 'B:f95c97ee09', }, }, }); diff --git a/yarn.lock b/yarn.lock index c7979e2..7928b78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4215,11 +4215,6 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== -"@types/uuid@^8.3.4": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -12991,6 +12986,11 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" From 2ea8494aa29f1d9c653d7e1da427c97274d5d73a Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Thu, 9 Jun 2022 09:35:01 -0400 Subject: [PATCH 3/3] fix sorting of helpers --- .vscode/launch.json | 2 +- packages/api/src/SymbolParser.ts | 6 ++---- packages/api/src/create-hash.ts | 8 ++++++++ packages/api/src/index.ts | 20 +++++++++++++++++++- 4 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 packages/api/src/create-hash.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 6e737e0..e6b3426 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "name": "api", "program": "${workspaceFolder}/node_modules/.bin/jest", "cwd": "${workspaceFolder}/packages/api", - "args": ["story-source"], + "args": ["-t", "'class generics'"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, diff --git a/packages/api/src/SymbolParser.ts b/packages/api/src/SymbolParser.ts index f10b273..7de7c0f 100644 --- a/packages/api/src/SymbolParser.ts +++ b/packages/api/src/SymbolParser.ts @@ -1,6 +1,5 @@ import * as ts from 'typescript'; import deepmerge from 'deepmerge'; -import hash from 'object-hash'; import { PropType, @@ -54,6 +53,7 @@ import { mergeNodeComments } from './jsdoc/mergeJSDoc'; import { parseJSDocTag } from './jsdoc/parseJSDocTags'; import { isTypeProp, ObjectProp } from './types'; import { ClassLikeProp, HasValueProp, SourcePositions } from '.'; +import { createHash } from './create-hash'; export class SymbolParser implements ISymbolParser { public checker: ts.TypeChecker; @@ -238,13 +238,11 @@ export class SymbolParser implements ISymbolParser { const location = this.getLocation(node, options); if (location && location.filePath && location.loc) { - const digest = hash({ + return createHash(fallback, { filePath: location.filePath, start: location.loc.start, end: location.loc.end, }); - - return `${fallback}:${digest.slice(0, 10)}`; } return fallback; diff --git a/packages/api/src/create-hash.ts b/packages/api/src/create-hash.ts new file mode 100644 index 0000000..2ef6968 --- /dev/null +++ b/packages/api/src/create-hash.ts @@ -0,0 +1,8 @@ +import hash from 'object-hash'; + +export function createHash( + name: string, + content: Record, +): string { + return `${name}:${hash(content).slice(0, 10)}`; +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 1a8e432..67122f9 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -9,6 +9,7 @@ import { getSymbolDeclaration, } from './ts-utils'; import { SymbolParser } from './SymbolParser'; +import { createHash } from './create-hash'; export * from './jsdoc'; export * from './ts'; @@ -185,7 +186,23 @@ export const analyzeFiles = ( if (collectHelpers) { // only return parents that are not already exported from the same file const helpers: Record = Object.keys(parser.helpers) - .filter((name) => parsed[name] === undefined) + .filter((helperName) => { + const { name, token } = parser.helpers[helperName]; + + if (options.collectHelpers && name) { + const parsedNode = parsed[name]; + return ( + parsedNode === undefined || + createHash(name, { + filePath: parsedNode.loc?.filePath, + start: parsedNode.loc?.loc?.start, + end: parsedNode.loc?.loc?.end, + }) !== token + ); + } + + return parsed[helperName] === undefined; + }) .reduce((acc, name) => ({ ...acc, [name]: parser.helpers[name] }), {}); if (Object.keys(helpers).length) { parsed = Object.keys(parsed).reduce((acc, key) => { @@ -194,6 +211,7 @@ export const analyzeFiles = ( [key]: consolidateParentProps([parsed[key]], helpers)[0], }; }, {}); + parsed.__helpers = Object.keys(helpers).reduce((acc, key) => { return { ...acc,