-
Notifications
You must be signed in to change notification settings - Fork 14
Add TypeScript declaration #15
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
Conversation
gagern
left a comment
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.
Thenk you for contributing this proposal. There are a number of items we should discuss before merging this, but I like where this is going and am sure we can work out the details.
README.md
Outdated
| ## TypeScript support | ||
|
|
||
| This library ships with a `.d.ts` for TypeScript users. | ||
| There is no need to find in `@types/` or roll your own \<3. |
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.
Are you willing to maintain the TypeScript support in the future? If not, I guess I'd find a formulation which expresses that this is not an integral part of the codebase, particularly since I'm not sure I can keep it up.
Either way I guess I'd get rid of the <3 eventually, as I prefer a drier style for public documentation and reserve hearts for personal communication. Unless you insist…
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 fine to maintain the TS support part (likely to use this library in a few more projects).
Feel free to change/remove the readme if you find it inconsistent :)
| "version": "0.3.2", | ||
| "description": "Node bindings to the HTML Tidy library", | ||
| "main": "src/index.js", | ||
| "types": "src/index.d.ts", |
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.
Feel free to also add yourself to the contributors section.
src/index.d.ts
Outdated
|
|
||
| /** | ||
| * TidyOption type and constructor (not for public use) | ||
| */ |
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.
If it's not for public use, then better leave this out. I had included it in the API docs for two reasons: to explain that TidyOption has a constructor, which can be used for instanceof checks and so on, and to explicitely state that the constructor should not be used. Not including it in the TS library would strengthen that message I think.
src/index.d.ts
Outdated
| parseBufferSync(document: Buffer): string | ||
| runDiagnosticsSync(): string | ||
| saveBufferSync(): Buffer | ||
| getErrorLog(): string |
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.
The getErrorLog method is currently not documented, because it should not be needed for normal operations. Every error should either be returned from a sync call or passed to the callback of an async call. It's just for the sake of a unit test that I allow access to this, to ensure we're not generating additional errors that we fail to report. The method may disappear at any moment. Perhaps I should rename it to _getErrorLog to communicate that idea.
src/index.d.ts
Outdated
| /************************************************************************ | ||
| * The Generated namespace is generated, and not meant for manual edit. | ||
| * | ||
| * @see https://gist.github.com/jokester/5c18601db8a9b290fabcfbab4cfc454b |
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.
Better include this script with the codebase. Feel free to create a directory called util and put it there. Bonus points if you can rig up recreation of this portion as part of the prepublish phase.
src/index.d.ts
Outdated
| * accessibility-check | ||
| */ | ||
| optGet(key: "accessibility-check" | "accessibility_check" | "AccessibilityCheck"): 0 | 1 | 2 | 3; | ||
| optSet(key: "accessibility-check" | "accessibility_check" | "AccessibilityCheck", value: 0 | 1 | 2 | 3): void; |
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.
The optGet part is incorrect: this version does return a string from the picklist. The optSet part is not exhaustive, as all of these integer-typed pick-value options accept integers or strings. Might be worth modeling.
src/index.d.ts
Outdated
|
|
||
| interface OptionDict { | ||
| indent_spaces?: number; | ||
| IndentSpaces?: number; |
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.
If you specify the dict like this, people may conceivably pass conflicting options to the constructor. I'd rather use a white lie and omit all the camel-cased names. That helps keep the sources consistent and avoids conflicts.
test/test-decl.ts
Outdated
| * | ||
| * required package (not included in package.json): | ||
| * - ts-node or tsc | ||
| * - @types/chai |
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.
Feel free to add these as dev dependencies, and to include these tests in the npm test machinery.
|
Oh, and one more thing: can you please conform to the two spaces of indentation I'm using? |
|
Still working 👷 , will let you know when I complete this list. I guess it will be before weekend.
|
instanceof still works
optGet: returns a string from picklist optSet: accepts index in picklist as well
|
@gagern Pushed a few more commits. Mind checking them? The generation script is included but not added to prepublish. I guess it safer to run |
|
@gagern Haven't hear from you recently, but .. do you think there are something we should do before merging this? |
gagern
left a comment
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.
Sorry for the delay. Still some questions left.
| "devDependencies": { | ||
| "@types/chai": "^3.5.0", | ||
| "@types/mocha": "^2.2.40", | ||
| "@types/node": "^7.0.12", |
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 wonder whether we should try to match the versions of these scoped packages here to those of the corresponding unscoped packages below. Any reasons in favor or against doing so?
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.
This one is a bit tricky, @types/ packages does not cover some of corresponding JS packages. Will see if I can fix or remove some packages.
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 was mostly referring to those which have unscoped counterparts. No need to change the set of files, just the versions.
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.
Sorry to say this, but the @types/mocha package does not have a 3.x version. The version 2.2 still it covers the mocha API we need.
Should I use this mismatched version, or remove this package and declare a minimal mocha API in place?
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 that case better use the mismatched version. As far as I can tell, the TypeScript dependencies don't pull in the actual libraries, so we shouldn't see much impact from the mismatch unless we start using API which is different between those versions. Right?
A Pull Request against the DefinitelyTyped might be a good idea if you want to, but I think we don't block on that here.
util/tsconfig.json
Outdated
| "atom": { | ||
| "rewriteTsconfig": false | ||
| } | ||
| } |
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.
Do we really need all of these settings? I'd prefer going with defaults as much as possible unless there is some major downside to doing so, in order to keep the number of settings that may need to be maintained low.
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.
It seems we can remove that file and still run the .ts code. Guess I should remove 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.
👍 to that.
README.md
Outdated
|
|
||
| This library ships with a `.d.ts` declaration for TypeScript users. | ||
|
|
||
| For contributors: the declaration is updated with the script in `util/gen-typescript-decl.ts`. |
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.
Or easier: “… updated by running npm run update-tsd”.
src/index.d.ts
Outdated
| * PrettyPrint / indent-spaces (integer) | ||
| */ | ||
| optGet(key: "indent-spaces" | "indent_spaces" | "IndentSpaces"): number; | ||
| optSet(key: "indent-spaces" | "indent_spaces" | "IndentSpaces", value: number): void; |
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.
Does this level of detail for the getters and setters really make sense? It does sound reasonable if you really are configuring these things manually, using string literals as keys. But those applications are easier to write using the options dictionary notation, I think. What happens if the configuration comes from a JSON file or similar? Is there some easy solution to make that work, too, or would that mean you'd have to type-cast the key from the JSON file to the enum of all the alternatives you have configured here? Would it make sense to add a catch-all definition up front? For the getter, that would be easy: optGet(key: string): number | string | boolean I think. For the setter that would become optSet(key: string, value: number | string | boolean) which would defy all the special cases, as this one case would capture anything not captured by the special cases, thus breaking the type checking there. So should we only do the specific getters, and the generic getter and setter? I'd like to see a generic get and set (using separately typed variables for key and value) demonstrated in the unit test.
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.
A value from JSON should still work without much effort: the TypeScript compiler (as of now) considers such a value to have any type, and excludes it from type check.
This is more of a limitation, those who read configure from JSON file cannot benefit from type checker.
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.
If I may say, adding generic getter / setter is not my favorite idea, mostly because allowing potentially incorrect option key is somehow against type checking. Can I ask what are some special cases that requires generic getter / setter?
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.
Correct me if I'm wrong, but here is what I imagine: When you read from JSON, the value would be of type any, but the key would be of type string, with no restriction as to what kind of string it would be. So you can't use the key from the JSON to configure these, unless you cast the string to something more generic. Right? I originally assumed you'd have to cast to the full list of enum values, but probably casting to any would be the more practical solution.
Another scenario might be query string parameters, which map string to string. As libtidy can parse strings, that's perfectly fine in terms of types, but would be problematic in a type-checked TypeScript.
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.
Guess I got your idea, but this one didn't go quite well. It seems that when optGet has a generic overload, its return type will always be number | string | boolean regardless of the callsite, for example:
// Type 'string | number | boolean' is not assignable to type 'boolean'.
const markup: boolean = doc.optGet("markup");(see this commit for detail)
Do you think we can go with specialized getters and generic setters?
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.
Sorry, lost track of this PR. Is there any reason why you'd write the above in that way, instead of
const markup: boolean = doc.options.markup;That way you could use the dict for specific access, and use the getters for generic access, i.e. things like this:
for (let o: doc.getOptionList())
console.log({[o.name]: doc.optGet(o.name)})Likewise, having all the various forms for values in the dictionary is harmful since you can't write things like
const check: string = doc.opts.accessibility_check;which I consider bad. Anyway, I finally got round to merging your PR, and did clean up the interface according to my taste in 0dc12f4.
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.
gagern: guess I did not consider that case. It looks much better with your fix, thanks!
util/gen-typescript-decl.ts
Outdated
| // The rest of this file is generated. | ||
| ` + generate(); | ||
|
|
||
| if (newContent === oldContent) { |
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.
Can you add a command line flag for “verify mode”, where you compare the content and exit with an error message and a non-zero exit status if there is a mismatch? Then we can have Travis run that mode to ensure the generated file stays in sync with the commited changes to the core library. This becomes moot if we autogenerate the code upon presubmit and leave it out of the repository.
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 getting a little confused here: if we output the generated code to a separated file, and do not include it in the repo, is a "verify mode" still needed?
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.
True. That's what I meant with by “this becomes moot”.
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.
Understood. Guess I should add the generation script to pretest hook 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.
Sounds good, yes.
src/index.d.ts
Outdated
| } | ||
| } | ||
|
|
||
| // START-GENERATED-NAMESPACE |
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.
Can you move this generated code to a separate file? I still believe not having the generated code in the repository, but generating it upon publication, would be the cleaner solution. That way we would not have to worry about inconsistent state, as there would be only a single source of truth, namely the tidy library. And with two files, it's easy to have one in the repository while ignoring the other. Automatically patching files is far more tricky in this respect.
|
Looks like a chicken and egg problem: you can't run the generator to build the options unless you already have the options build by the generator. One solution would be having the generator written in plain JavaScript. Another would be handling the option verification somewhere else, e.g. in a unit test. |
|
Thanks for pointing out. I was able to reproduce the chicken-egg problem in VS Code editor (strangely, not when running the script). Guess changing the script to JavaScript is the safer fix. |
This
index.d.tsshould be sufficient for most library consumers in TypeScript.There are missing points though. To name a few:
escape-ScriptoptGet / optSetwith numerical option id. They are hard to remember and unstable across versions so I didn't generate overloads for them.