Skip to content
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

add type checking & declarations #7

Merged
merged 2 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tmp
node_modules/
.git
3 changes: 0 additions & 3 deletions .eslintrc

This file was deleted.

28 changes: 28 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"extends": "groupon/node8",
"overrides": [
{
"files": "*.mjs",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"node/no-unsupported-features": [
"error",
{
"version": 8,
"ignores": [
"modules"
]
}
]
}
},
{
"files": "*.test.js",
"env": {
"mocha": true
}
}
]
}
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ignore-engines=true
registry=https://registry.npmjs.org
10 changes: 5 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
language: node_js
node_js:
- 6.14.3
- 8.11.3
- 10.5.0
- 8
- 10
- 12
deploy:
- provider: script
script: ./node_modules/.bin/nlm release
script: npx nlm release
skip_cleanup: true
'on':
branch: master
node: 10.5.0
node: 12
before_install:
- npm i -g npm@^6
132 changes: 78 additions & 54 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Generated by generator-js -->
<!-- Generated by npm init @grpn -->

# Contributing

Expand All @@ -14,7 +14,7 @@ feel free to [open an issue](#reporting-issues)
### Reporting Issues

If you find any mistakes in the docs or a bug in the code,
please [open an issue in GitHub](https://github.com/groupon/phy/issues/new) so we can look into it.
please [open an issue in Github](https://github.com/groupon/phy/issues/new) so we can look into it.
You can also [create a PR](#contributing-code) fixing it yourself, or course.

If you report a bug, please follow these guidelines:
Expand All @@ -23,18 +23,22 @@ If you report a bug, please follow these guidelines:
* Include instructions on how to reproduce the issue.
The instructions should be as minimal as possible
and answer the three big questions:
1. What are the exact steps you took? This includes the exact versions of node, npm, and any packages involved.
1. What are the exact steps you took? This includes the exact versions of
node, npm, and any packages involved.
1. What result are you expecting?
1. What is the actual result?

### Improving Documentation

For small documentation changes, you can use [Github's editing feature](https://help.github.com/articles/editing-files-in-another-user-s-repository/).
For small documentation changes, you can use [Github's editing feature][ghedit].
The only thing to keep in mind is to prefix the commit message with "docs: ".
The default commit message generated by Github will lead to a failing CI build.

[ghedit]: https://help.github.com/articles/editing-files-in-another-user-s-repository/

For larger updates to the documentation
it might be better to follow the [instructions for contributing code below](#contributing-code).
it might be better to follow the
[instructions for contributing code below](#contributing-code).

### Contributing Code

Expand All @@ -45,40 +49,41 @@ only to discover that it conflicts with plans the maintainers might have.

The general steps for creating a pull request are:

1. Create a branch for your change.
Always start your branch from the latest `master`.
We often prefix the branch name with our initials, e.g. `jk-a-change`.
1. Run `npm install` to install the dependencies.
1. If you're fixing a bug, be sure to write a test *first*.
That way you can validate that the test actually catches the bug and doesn't pass.
1. Make your changes to the code.
Remember to update the tests if you add new features or change behavior.
1. Run the tests via `npm test`. This will also run style checks and other validations.
You might see errors about uncommitted files.
This is expected until you commit your changes.
1. Once you're done, `git add .` and `git commit`.
Please follow the [commit message conventions](#commits--commit-messages) described below.
1. Create a branch for your change. Always start your branch from the latest
`master`. We recommend using `git wf start some-feature-name` by using
[git workflow][gitwf]. Run `npm install` to install the dependencies.
1. If you're fixing a bug, be sure to write a test *first*. That way you can
validate that the test actually catches the bug and doesn't pass.
1. Make your changes to the code. Remember to update the tests if you add new
features or change behavior.
1. Run the tests via `npm test`. This will also run style checks and other
validations. You might see errors about uncommitted files. This is
expected until you commit your changes.
1. Once you're done, `git add .` and `git commit`. Please follow the
[commit message conventions](#commits--commit-messages) described below.
1. Push your branch to Github & create a PR.

[gitwf]: https://github.com/groupon/git-workflow

#### Code Style

In addition to any linting rules the project might include,
a few general rules of thumb:
In addition to any linting rules the project might include, a few general rules
of thumb:

* Try to match the style of the rest of the code.
* We prefer simple code that is easy to understand over terse, expressive code.
* We try to structure projects by semantics instead of role.
E.g. we'd rather have a `tree.js` module that contains tree traversal-related helpers
than a `helpers.js` module.
* Actually, if you create helpers you might want to put those into a separate package.
That way it's easier to reuse them.
* We try to structure projects by semantics instead of role. E.g. we'd rather
have a `tree.js` module that contains tree traversal-related helpers than
a `helpers.js` module.
* Actually, if you create helpers you might want to put those into a separate
package. That way it's easier to reuse them.

#### Commits & Commit Messages

Please follow the [angular commit message conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines).
We use an automated tool for generating releases
that depends on the conventions to determine the next version and the content of the changelog.
Commit messages that don't follow the conventions will cause `npm test` (and thus CI) to fail.
Please follow the [angular commit message conventions][angular-commits]. We
use an automated tool for generating releases that depends on the conventions
to determine the next version and the content of the changelog. Commit messages
that don't follow the conventions will cause `npm test` (and thus CI) to fail.

The short summary - a commit message should look like this:

Expand All @@ -92,37 +97,55 @@ The short summary - a commit message should look like this:
<footer>
```

Everything but the first line is optional.
The empty lines between the different parts are required.
Everything but the first line is optional. The empty lines between the
different parts are required.

* `<type>`: One of the following:
- **feat:** Introduces a new feature. This will cause the minor version to go up.
- **fix:** A bug fix. Causes a patch version bump.
- **docs:** Changes to the documentation.
This will also cause an increase of the patch version so that the changes show up in the npm registry.
- **style:** Cleanup & lint rule fixes.
Note that often it's better to just amend the previous commit if it introduced lint errors.
- **refactor:** Changes to the code structure without fixing bugs or adding features.
- **feat:** Introduces a new feature. This will cause the minor version to go
up. If this commit adds a feature to this module's public API, it's a
`feat:`
- **fix:** A bug fix. Causes a patch version bump. A new feature is not (by
itself) a fix.
- **docs:** Changes to the documentation. This will also cause an increase of
the patch version so that the changes show up in the npm registry.
- **style:** Syntax cleanup & lint rule fixes. Note that often it's better to
just amend the previous commit if it introduced lint errors, and tools
like `prettier` should be taking care of most `style` issues.
- **refactor:** Changes to the code structure without fixing bugs or adding
features. If the functionality is unchanged, but you're touching active
code in the package, it's a `refactor`.
- **perf:** Performance optimizations.
- **test:** Fixing existing tests or adding missing ones.
Just like with **style**, if you add tests to a feature you just introduced in the previous commit,
consider keeping the tests and the feature in the same commit instead.
- **chore:** Changes to the project setup and tools, dependency bumps, house-keeping.
* `<subject>`: A [good git commit message subject](http://chris.beams.io/posts/git-commit/#limit-50).
- Keep it brief. If possible the whole first line should have at most 50 characters.
Just like with **style**, if you add tests to a feature you just
introduced in the previous commit, consider keeping the tests and the
feature in the same commit instead.
- **chore:** Changes to the project setup and tools, dependency bumps,
and package-building house-keeping. `chore` is not a judgement on how you
feel about a given work. Changes to actual code are **NEVER** `chore`
commits. If you're fixing, refactoring, or adding a feature, that is
by definition a `fix`, `refactor`, or `feat`. If you're upgrading some
dev dependency, fiddling with the package.json author field, etc., that's
a `chore` commit.
* `<subject>`: A [good git commit message subject][limit50].
- Keep it brief. If possible the whole first line should have at most 50
characters.
- Use imperative mood. "Create" instead of "creates" or "created".
- No period (".") at the end.
* `<body>`: Motivation for the change and any context required for understanding the choices made.
Just like the subject, it should use imperative mood.
* `<references>`: Any URLs relevant to the PR go here.
Use one line per URL and prefix it with the kind of relationship, e.g. "Closes: " or "See: ".
If you are referencing an issue in your commit body or PR description,
never use `#123` but the full URL to the issue or PR you are referencing.
That way the reference is easy to resolve from the git history without having to "guess" the correct link
even if the commit got cherry-picked or merged into a different project.
* `<footer>`: This part only applies if your commit introduces a breaking change.
It's important this is present, otherwise the major version will not increase.
See below for an example.
* `<body>`: Motivation for the change and any context required for understanding
the choices made. Just like the subject, it should use imperative mood.
* `<references>`: Any URLs relevant to the PR go here. Use one line per URL and
prefix it with the kind of relationship, e.g. "Closes: " or "See: ". If you
are referencing an issue in your commit body or PR description, never use
`#123` but the full URL to the issue or PR you are referencing. That way
the reference is easy to resolve from the git history without having to
"guess" the correct link even if the commit got cherry-picked or merged
into a different project.
* `<footer>`: This part only applies if your commit introduces a breaking
change. It's important this is present, otherwise the major version will
not increase. See below for an example.

[angular-commits]: https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commit-message-format
[limit50]: http://chris.beams.io/posts/git-commit/#limit-50

##### Examples

Expand All @@ -145,3 +168,4 @@ A simple bug fix:
```
fix: Handle multi-byte characters in search logic
```

68 changes: 38 additions & 30 deletions lib/phy.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,18 @@

const preact = require('preact');

/**
* @typedef {preact.VNode} VNode
* @typedef {preact.ComponentType} ComponentType
* @typedef {preact.ComponentChildren} ComponentChildren
*/

// VNode props https://github.com/preactjs/preact/blob/master/src/index.d.ts#L14-L17
const VNODE_ATTRS = ['props', 'type', 'key', 'ref'];

// simplified from lodash
const objectToString = Object.toString();

/** @param {VNode | Readonly<Record<string, any>>} obj */
function isAttributes(obj) {
return (
'object' === typeof obj &&
Expand All @@ -47,50 +54,51 @@ function isAttributes(obj) {
);
}

const re = /^([a-zA-Z\d-]+)|([.#])([\w-]+)/g;

// possible arg combos (eliding createElement) (kids* = 0-or-more-kids):
// h(selector: Component|string, kids*: string|Element|Array[kid])
// h(selector: Component|string, attrs: object|null, kids*: string|Element|Array[kid])
function h(createElement, selector, attrs) {
const kids = Array.from(arguments).slice(3);

/**
* @param {typeof preact.createElement} createElement
* @param {string | ComponentType} selector
* @param {Readonly<Record<string, any>>} [attrs]
* @param {ComponentChildren[]} kids
*/
function h(createElement, selector, attrs, ...kids) {
if (attrs) {
if (!isAttributes(attrs)) {
kids.unshift(attrs);
kids.unshift(/** @type {ComponentChildren} */ (attrs));
attrs = {};
}
} else attrs = {};

if ('string' !== typeof selector) return createElement(selector, attrs, kids);
if ('string' !== typeof selector) {
return createElement(selector, attrs, ...kids);
}

let tag = 'div';
let classes;

const { class: klass, className, ...restAttrs } = attrs;
const attrClass = klass || className;
/** @type {Set<string>} */
const classes = new Set(attrClass ? attrClass.trim().split(/\s+/) : []);

// recast to remove readonly
/** @type {Record<string, any>} */
const attrsOut = restAttrs;

const re = /^([a-zA-Z\d-]+)|([.#])([\w-]+)/g;
let m;
while ((m = re.exec(selector))) {
if (m[1]) tag = m[1];
else if (m[2] === '#') attrs.id = m[3];
else {
if (!classes) {
classes = attrs.class || attrs.className;
classes = classes
? classes.split(/\s+/).reduce((o, c) => {
o[c] = true;
return o;
}, {})
: {};
}
classes[m[3]] = true;
}
}
if (classes) {
attrs.class = Object.keys(classes).join(' ');
delete attrs.className;
const [, explicitTag, sep, classOrId] = m;
if (explicitTag) tag = explicitTag;
else if (sep === '#') attrsOut.id = classOrId;
else classes.add(classOrId);
}

return createElement.apply(null, [tag, attrs].concat(kids));
if (classes.size > 0) attrsOut.class = [...classes].join(' ');

return createElement(tag, attrsOut, ...kids);
}

/** @type {import('./typedefs')} */
module.exports = exports = h.bind(null, preact.createElement);

exports.h = exports;
Expand Down
19 changes: 17 additions & 2 deletions lib/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,27 @@

'use strict';

/**
* @param {typeof import('../').h} h
* @param {string} tag
* @param {string} [selector]
* @param {Readonly<Record<string, any>>} [attrs]
* @param {import('preact').ComponentChildren[]} kids
*/
function hhTags(h, tag, selector, attrs, kids) {
if ('string' === typeof selector) {
if (!/^[.#]/.test(selector))
// e.g. span('oops')
if (!/^[.#]/.test(selector)) {
throw new Error(`${tag}(): string children must be passed in an array`);
}
// e.g. span('#photo', { alt: 'photo spot' }, ['photo here'])
// or span('#photo', ['photo here']) or span('#photo')
return h(`${tag}${selector}`, attrs, kids);
}
// no selector given, pass next two args shifted

// no selector given, pass remaining args shifted
// e.g. span({ alt: 'photo spot' }, ['photo here'])
// or span(['photo here'])
return h(tag, selector, attrs);
}

Expand All @@ -52,5 +66,6 @@ const TAG_NAMES =
exports.h = h;

TAG_NAMES.split('|').forEach(tag => {
// @ts-ignore
exports[tag] = hh.bind(null, tag);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the type declarations for this will be huge, since we'll have to explicitly split out all the tags; if we end up adding that later, we'll fix up the types in this file

});
Loading