-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(types): add type definitions #24
Conversation
[key: string]: any // @TODO - can this be anything but a string? it technically SHOULD be, | ||
// but I'm not sure if we want to be that strict. maybe it should be primitives at least? | ||
// or maybe we just leave it alone? | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
I'm unsure about naming in general - whether to be verbose like I've done here, or to be simple like
Attributes
andNode
orElement
etc -
attribute values are a bit tricky. Anything parsed from an HTML will be in string format, always. Any JsonML attributes converted back into HTML or DOM are coerced to strings.
But does that mean that, in the interim, they must be strings? Also, I know there are plenty of use cases where just pragmatically the values have been modified and altered and are definitely NOT strings. I think I'm in favor of leaving this set to
any
rather than imposing strictness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I frequently import a more generally named type as a "namespaced" import name if need be, I think you be alright to name simply.
- It might be nice to see this winnowed down to something other than
any
. Perhaps a primitive union namedAttribute
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I've gone w/ the generic naming.
- For values, I've defined a type to centralize the issue but I'm still debating a bit. I suspect our application usage (appending things to JsonML structures) will potentially conflict with the strict value here. Pragmatically, I worry this could cause some problems for us in practice and I'm wary of implementing it. In theory, any value could be ok if it as a
toString()
implementation anyway. This library could export both types for consuming applications to optionally make use of? 🤷♂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I'm suggesting, for example:
type Attribute = string | number | <other primitive types here>;
The problem I see with using any
is you open the doors to object literals and on and on, things which will not, to your point, have a toString()
implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question
: Ah...I see you have AttributeValueStrict
now. Why are you not using it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not using it because I think it will break our applications.
I'm pretty sure we are extending element attributes with objects within our applications, since we don't intend to transform the JsonML back into HTML or DOM. An example of a common use case might be an embedded content reference where later we merge in the related data structures directly with the attributes.
A likely use case, in the wild:
['inline-embed', { type: 'foo', relatedData: {/* some object */}, }]
Enforcing this type strictness is correct per the spec but might become a problem for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave this some more thought. This fork is open-source and shouldn't care about how downstream applications. The type definitions should be strict and accurate. If we need to find some workarounds later, we can figure that out. I've updated the attribute value definition accordingly.
Update: still more struggle. I'm testing this and it does indeed cause a great deal of trouble. Far more than it's worth, I'd say. Once I define strict attribute values, this is required by the Element
definition and used everywhere. As a result, if an application deviates from the expected attribute value, everything fails and the application won't be able to make use of these exported definitions (they will have to be duplicated and modified for application use).
Thoughts? I feel like the most strictly correct thing to do is to keep the strict definitions and then duplicate and modify the definitions file as needed by applications. However, I worry about the trade-off in value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my experience, its pretty common to have strict types imported as correctly typed by a lib and then extend, union, or partial them in my code. Here's a paired down example of how this might work in this case: Playground Link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, agreed. The trouble is that the AttributeValue
will be used as part of other type definitions in this file. That's where it all goes sideways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeromecovington @pgoldrbx yeah, I think for now we may just want to use any
for pragmatic reasons since downstream consumers haven't adhered to the spec when performing manual transformations of the JsonML after the initial document conversion to JsonML from Markdown. If we're wanting to strictly adhere to the spec, we may want to do this in a two-phase approach, where the first is to publish a version using any
here and then issue a breaking change that follows the spec, giving consumers a chance to adjust approach on those not-to-spec implementations
index.d.ts
Outdated
|
||
export interface JsonMLNode extends Array<string | JsonMLAttributes | JsonMLChildNode> { | ||
0: string; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I worked this out finally!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question
: Do you actually need separate types here? Why not something like:
interface ElementMaybeWithAttributes extends Array<TagName | Attributes | Element | undefined> {
0: TagName;
1?: Attributes;
}
test/types.test.ts
Outdated
|
||
// Utils | ||
const isFragYes: boolean = utils.isFragment(['', 'hi']); | ||
const isFragNo: boolean = utils.isFragment(['div', 'hi']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way to assert types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion
: You might like to take a look at using assertion functions. There are various options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion
: Another less obvious option would be to add a test script which exercises the types using the TypeScript compiler. Here's an example from core-logbus-sdk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't make use of assert
since (currently) this file is only run through tsc
and not executed at runtime. Additionally, I guess I'd have to figure out how to specify assert/node types in the config?
I did look at other repo examples, and the logbus is another good reference
https://github.com/CondeNast-Copilot/core-logbus-sdk/blob/master/test/assert-types.ts
It feels somewhat similar to what I'm doing - which is essentially just trying to setup all the calls and objects. I went the extra difference of assigning the return value so the type checker would catch this also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few initial thoughts and comments, hopefully they are useful!
[key: string]: any // @TODO - can this be anything but a string? it technically SHOULD be, | ||
// but I'm not sure if we want to be that strict. maybe it should be primitives at least? | ||
// or maybe we just leave it alone? | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I frequently import a more generally named type as a "namespaced" import name if need be, I think you be alright to name simply.
- It might be nice to see this winnowed down to something other than
any
. Perhaps a primitive union namedAttribute
?
index.d.ts
Outdated
|
||
export interface JsonMLNode extends Array<string | JsonMLAttributes | JsonMLChildNode> { | ||
0: string; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
index.d.ts
Outdated
|
||
export function addAttributes(jml: JsonMLNode, attr: JsonMLAttributes): undefined; | ||
|
||
export function getAttribute(jml: JsonMLNode, key: string): any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion
: Here, you could use the suggested Attribute
as return type.
index.d.ts
Outdated
|
||
export function getAttributes(jml: JsonMLNode, addIfMissing?: boolean): JsonMLAttributes; | ||
|
||
export function addAttributes(jml: JsonMLNode, attr: JsonMLAttributes): undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion
: I may be mistaken but I think the return value here would more properly be void
. I see this elsewhere in the utils
namespace as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yeah! thanks
test/types.test.ts
Outdated
|
||
// Utils | ||
const isFragYes: boolean = utils.isFragment(['', 'hi']); | ||
const isFragNo: boolean = utils.isFragment(['div', 'hi']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion
: You might like to take a look at using assertion functions. There are various options.
test/types.test.ts
Outdated
|
||
// Utils | ||
const isFragYes: boolean = utils.isFragment(['', 'hi']); | ||
const isFragNo: boolean = utils.isFragment(['div', 'hi']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion
: Another less obvious option would be to add a test script which exercises the types using the TypeScript compiler. Here's an example from core-logbus-sdk.
140d314
to
5a51905
Compare
// | ||
// `Node` type imported from lib.dom | ||
// | ||
/// <reference lib="dom"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempting to mark that lib.dom
is required. The Node
type is available as a global after adding 'dom'
to tsconfig
. I wish I could reference it more explicitly.
Overall, I'm not sure if I'm handling namespacing/scope properly.
I've also renamed types to avoid shadowing the DOM globals.
Element
->
JSONMLElement
Node
->
JSONMLNode
(based on similar from HTMLElement
)
| [ TagName, Attributes ] | ||
| [ TagName, ...JSONMLNode[] ] | ||
| [ TagName, Attributes, ...JSONMLNode[] ] | ||
; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeromecovington I've tried another approach! I think I have variable-length tuples working now by using spread. 🤷♂
['p', { foo: 'bar' }, 'some fragment text'], | ||
['div', 'foo'], | ||
'baz' | ||
]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and can verify that I get issues if the attributes are moved to the wrong position or if an undefined
or otherwise invalid element is added somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilius @jeromecovington could I get another review on this?
closing as abandoned |
Description
Initial progress toward adding type definitions to the package. The utils module feels reasonably well satisfied, with a few questions. The DOM and HTML types are handled using
lib.dom
(is this done correctly?Grammar used for reference: http://www.jsonml.org/syntax/
Types of changes
Related Issue
Motivation and Context
This library is being used in applications built with typescript. Those applications have begun defining JsonML types and interfaces locally when it would make more sense to move them here into this package.
How Has This Been Tested?
Screenshots (if appropriate):
Checklist: