diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f497382..56986d68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,3 +31,5 @@ jobs: yarn cd packages/analyzer npm test + cd ../helpers + npm test diff --git a/packages/helpers/README.md b/packages/helpers/README.md new file mode 100644 index 00000000..afa73b2b --- /dev/null +++ b/packages/helpers/README.md @@ -0,0 +1,149 @@ +# @custom-elements-manifest/helpers + +Custom-elements.json is a file format that describes custom elements. This format will allow tooling and IDEs to give rich information about the custom elements in a given project. + +This library aims to ship some helpers to ease working with the `custom-elements.json` format. + +## Install + +```bash +npm i -S @custom-elements-manifest/helpers +``` + +## Usage + +```js +// Node +import { CustomElementsJson } from '@custom-elements-manifest/helpers'; + +const manifest = JSON.parse(fs.readFileSync('./custom-elements.json', 'utf-8')); +const customElementsJson = new CustomElementsJson(manifest); + +// Browser +import { CustomElementsJson } from 'https://unpkg.com/@custom-elements-manifest?module'; +import manifest from './custom-elements.json' assert { type: 'json' }; + +const customElementsJson = new CustomElementsJson(manifest); +``` + +## Methods + +### `getByTagName` + +Gets a declaration by tagName + +```js +customElementsJson.getByTagName('my-element'); +``` + +### `getByClassName` +Get a declaration by className + +```js +customElementsJson.getByClassName('MyElement'); +``` + +### `getByMixinName` +Gets a declaration by mixinName + +```js +customElementsJson.getByMixinName('MyElement'); +``` +### `getCustomElements` +Gets all custom elements + +```js +customElementsJson.getCustomElements(); +``` + +### `getClasses` +Gets all classes. Note that this may include classes that are not custom elements + +```js +customElementsJson.getClasses(); +``` + +### `getFunctions` +Gets all functions + +```js +customElementsJson.getFunctions(); +``` + +### `getVariables` +Gets all variables + +```js +customElementsJson.getVariables(); +``` + +### `getDefinitions` +Gets all custom element definitions. + +```js +customElementsJson.getDefinitions(); +``` + +### `getMixins` +Gets all mixins + +```js +customElementsJson.getMixins(); +``` + +### `getInheritanceTree` + +Gets an elements inheritance tree, including superclasses and mixins + +```js +customElementsJson.getInheritanceTree('MyElement'); +``` + +### `getModuleForClass` + +Gets the module path for a given class + +```js +customElementsJson.getModuleForClass('MyElement'); +``` + +### `getModuleForMixin` + +Gets the module path for a given mixin + +```js +customElementsJson.getModuleForMixin('FooMixin'); +``` + +## Helpers + +### Package +- `hasModules` +- `hasExports` +- `hasDeclarations` +- `isJavascriptModule` + +### Exports +- `isCustomElementExport` +- `isJavaScriptExport` + +### Declarations +- `isClass` +- `isMixin` +- `isCustomElement` +- `isFunction` +- `isVariable` + +### CustomElement +- `hasAttributes` +- `hasCssParts` +- `hasCssProperties` +- `hasEvents` +- `hasSlots` +- `hasMethods` +- `hasFields` +- `hasMixins` + +### ClassMember +- `isField` +- `isMethod` \ No newline at end of file diff --git a/packages/helpers/helpers.js b/packages/helpers/helpers.js new file mode 100644 index 00000000..cc97c937 --- /dev/null +++ b/packages/helpers/helpers.js @@ -0,0 +1,97 @@ +export const has = arr => Array.isArray(arr) && arr?.length > 0; + +/** Package */ +export function hasModules(_package) { + return has(_package?.modules); +} + +/** JavaScriptModule */ +export function hasExports(_module) { + return has(_module?.exports); +} + +export function hasDeclarations(_module) { + return has(_module?.declarations); +} + +export function isJavaScriptModule(_module) { + return _module.kind === 'javascript-module'; +} + +/** Exports */ +export function isCustomElementExport(_export) { + return _export.kind === 'custom-element-definition'; +} + +export function isJavaScriptExport(_export) { + return _export.kind === 'js'; +} + +/** Declarations */ +export function isClass(item) { + return item.kind === 'class'; +} + +export function isMixin(item) { + return item.kind === 'mixin'; +} + +export function isCustomElement(item) { + return item.customElement; +} + +export function isFunction(item) { + return item.kind === 'function'; +} + +export function isVariable(item) { + return item.kind === 'variable'; +} + +/** CustomElement */ +export function hasAttributes(customElement) { + return has(customElement?.attributes) +} + +export function hasCssParts(customElement) { + return has(customElement?.cssParts) +} + +export function hasCssProperties(customElement) { + return has(customElement?.cssProperties) +} + +export function hasEvents(customElement) { + return has(customElement?.events) +} + +export function hasSlots(customElement) { + return has(customElement?.slots) +} + +export function hasMethods(customElement) { + return ( + has(customElement?.members) && + customElement?.members?.some(member => member.kind === 'method') + ); +} + +export function hasFields(customElement) { + return ( + has(customElement?.members) && + customElement?.members?.some(member => member.kind === 'field') + ); +} + +export function hasMixins(customElement) { + return has(customElement?.mixins); +} + +/** ClassMember */ +export function isField(member) { + return member.kind === 'field'; +} + +export function isMethod(member) { + return member.kind === 'method'; +} \ No newline at end of file diff --git a/packages/helpers/index.js b/packages/helpers/index.js new file mode 100644 index 00000000..a5cbf000 --- /dev/null +++ b/packages/helpers/index.js @@ -0,0 +1,192 @@ +import * as h from './helpers.js'; + +export class CustomElementsJson { + _functions = new Map(); + _variables = new Map(); + _classes = new Map(); + _tagNames = new Map(); + _definitions = new Map(); + _mixins = new Map(); + _classLikes = new Map(); + _customElements = new Map(); + + constructor(cem) { + for (const [key, value] of Object.entries(cem)) { + this[key] = value; + } + this.init(); + } + + init() { + this.loopAll((item) => { + if (h.isClass(item)) { + this._classes.set(item.name, item); + this._classLikes.set(item.name, item); + } + + if(h.isFunction(item)) { + this._functions.set(item.name, item); + } + + if(h.isVariable(item)) { + this._variables.set(item.name, item); + } + + if(h.isCustomElement(item)) { + this._customElements.set(item.name, item); + } + + if (h.isMixin(item)) { + this._mixins.set(item.name, item); + this._classLikes.set(item.name, item); + } + }); + + this.loopAll((item) => { + if (h.isCustomElementExport(item)) { + this._tagNames.set( + item.name, + this._classes.get(item.declaration.name), + ); + + this._definitions.set(item.name, item); + } + }); + } + + loopAll(cb) { + this.modules.forEach((mod) => { + mod?.exports?.forEach((ex) => { + cb(ex); + }); + + mod?.declarations?.forEach((declaration) => { + cb(declaration); + }); + }); + } + + getByTagName(tagName) { + return this._tagNames.get(tagName); + } + + getByClassName(className) { + return this._classes.get(className); + } + + getByMixinName(mixinName) { + return this._mixins.get(mixinName); + } + + /** Gets all customElements from declarations */ + getCustomElements() { + return [...this._customElements.values()]; + } + + /** Gets all functions from declarations */ + getFunctions() { + return [...this._functions.values()]; + } + + /** Gets all functions from declarations */ + getVariables() { + return [...this._variables.values()]; + } + + /** Gets all classes from declarations */ + getClasses() { + return [...this._classes.values()]; + } + + /** Gets all CustomElementDefinitions */ + getDefinitions() { + return [...this._definitions.values()]; + } + + getMixins() { + return [...this._mixins.values()]; + } + + // @TODO + getInheritanceTree(className) { + const tree = []; + + let klass = this._classLikes.get(className); + const mixins = this.getMixins(); + + if(klass) { + tree.push(klass); + + klass?.mixins?.forEach(mixin => { + let foundMixin = mixins.find(m => m.name === mixin.name); + if(foundMixin) { + tree.push(foundMixin); + + while(h.has(foundMixin?.mixins)) { + foundMixin?.mixins?.forEach(mixin => { + foundMixin = mixins.find(m => m.name === mixin.name); + if(foundMixin) { + tree.push(foundMixin); + } + }); + } + } + }); + + while(this._classLikes.has(klass?.superclass?.name)) { + const newKlass = this._classLikes.get(klass.superclass.name); + + klass?.mixins?.forEach(mixin => { + let foundMixin = mixins.find(m => m.name === mixin.name); + if(foundMixin) { + tree.push(foundMixin); + + while(h.has(foundMixin?.mixins)) { + foundMixin?.mixins?.forEach(mixin => { + foundMixin = mixins.find(m => m.name === mixin.name); + if(foundMixin) { + tree.push(foundMixin); + } + }); + } + } + }); + + tree.push(newKlass); + klass = newKlass; + } + + return [...new Map(tree.map(item => [item.name, item])).values()]; + } + return []; + + } + + getModuleForClass(className) { + let result = undefined; + + this.modules.forEach((mod) => { + mod?.declarations?.forEach((declaration) => { + if (h.isClass(declaration) && declaration.name === className) { + result = mod.path; + } + }); + }); + + return result; + } + + getModuleForMixin(className) { + let result = undefined; + + this.modules.forEach((mod) => { + mod?.declarations?.forEach((declaration) => { + if (h.isMixin(declaration) && declaration.name === className) { + result = mod.path; + } + }); + }); + + return result; + } +} diff --git a/packages/helpers/package-lock.json b/packages/helpers/package-lock.json new file mode 100644 index 00000000..661dda97 --- /dev/null +++ b/packages/helpers/package-lock.json @@ -0,0 +1,279 @@ +{ + "name": "helpers", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "executing-npm-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/executing-npm-path/-/executing-npm-path-1.0.0.tgz", + "integrity": "sha512-d/dZlFCLkKm8nwdzpfQ7JBL2BISg4Fu0bVpZ5nacuT3e6DIxYVb+8tx0eQ+jxquvV/8I+VjJ9g6aEAqjukogkw==", + "dev": true + }, + "find-pkg-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-pkg-dir/-/find-pkg-dir-1.0.1.tgz", + "integrity": "sha512-pIXIrZshXst3hpg5nXDYALHlN4ikh4IwoM0QRnMnYIALChamvpPCJS1Mpwp27GpXXTL/647LDS4JkH1yfAKctw==", + "dev": true, + "requires": { + "inspect-with-kind": "^1.0.4" + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "import-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-package/-/import-package-1.0.0.tgz", + "integrity": "sha512-EEDT2ucOWI/9z/h2mLTRkkusM30/pxSoBT6YvomYpEb/UGll6wOvdFagYraCSBHD+dZSKoVFNYCAgC3V7Nvf1Q==", + "dev": true, + "requires": { + "load-from-cwd-or-npm": "^3.0.1" + } + }, + "inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true + }, + "load-from-cwd-or-npm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/load-from-cwd-or-npm/-/load-from-cwd-or-npm-3.0.4.tgz", + "integrity": "sha512-tdDJgh1zVmxOV24gcj+AEagjTc30Jim9ywX2OxfABdOoTU4UK8b0B371ZNbpj27njP8LQ6U5FH1aNwC2+VRxQg==", + "dev": true, + "requires": { + "inspect-with-kind": "^1.0.5", + "npm-cli-dir": "^3.0.1", + "optional": "^0.1.4", + "resolve-from-npm": "^3.1.0" + } + }, + "mri": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", + "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", + "dev": true + }, + "npm-cli-dir": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-cli-dir/-/npm-cli-dir-3.0.1.tgz", + "integrity": "sha512-t9V9Gz/Q5a5KOSynLpKKnLxJzWLnHtAZvaLmNSbNeNR+qEpCmu/n5J74lyz4QQ/XIGEEYWIoVXR8scqbUWaMrQ==", + "dev": true, + "requires": { + "find-pkg-dir": "^1.0.1", + "npm-cli-path": "^3.1.0" + } + }, + "npm-cli-path": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/npm-cli-path/-/npm-cli-path-3.1.2.tgz", + "integrity": "sha512-JdiFz8kpCf9WD01zRx5u29EP5UYjKp9osSVMflPkamlplgsuaagkwqY3JpzDySl/VDpGUva8q8YoSG6AatFkIg==", + "dev": true, + "requires": { + "executing-npm-path": "^1.0.0", + "which": "^1.3.1", + "win-user-installed-npm-cli-path": "^3.0.0" + } + }, + "npm-cli-version": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-cli-version/-/npm-cli-version-1.0.0.tgz", + "integrity": "sha512-VqqnMzMfcZ0UZFDki7ZR8E4U8Pz7VbTOGSMk8KJbQ+oUlJlon8IXhb6BIdMJClRArHn216useYM1kvqgZmDvtQ==", + "dev": true, + "requires": { + "npm-cli-dir": "^3.0.0" + } + }, + "npm-version-compare": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-version-compare/-/npm-version-compare-1.0.1.tgz", + "integrity": "sha512-X+/Oz2OkF6KzMqyFyNBV5MC1ScPxtl5bJTkUcIp9Bz5Wv2Yf8uqDIq+vu+/gy2DRb11Q2Z6jfHbav7Ux0t99JQ==", + "dev": true, + "requires": { + "import-package": "^1.0.0", + "inspect-with-kind": "^1.0.5", + "npm-cli-version": "^1.0.0" + } + }, + "optional": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", + "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "platform-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/platform-name/-/platform-name-1.0.0.tgz", + "integrity": "sha512-ZRbqJ30uRRKGKW2O1XnG/Ls1K/aBGlnyjq1Z0BbjqDPTNN+XZKFaugCsCm3/mq6XGR5DZNVdV75afpQEvNNY3Q==", + "dev": true, + "requires": { + "inspect-with-kind": "^1.0.4" + } + }, + "reject-unsatisfied-npm-version": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reject-unsatisfied-npm-version/-/reject-unsatisfied-npm-version-1.0.0.tgz", + "integrity": "sha512-8cl35x8i3W1+RubvIq9CM7fJkdMwBOdjne4b7eFBoo4vvN1QoXbgusQw6VVv2DBmm6NDyMhUmp9FBxaMWU9s7Q==", + "dev": true, + "requires": { + "npm-cli-version": "^1.0.0", + "npm-version-compare": "^1.0.0" + } + }, + "resolve-from-npm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/resolve-from-npm/-/resolve-from-npm-3.1.0.tgz", + "integrity": "sha512-HVhEcznfeFWM7T3HWCT7vCjwkv0R1ruC4Ref5jTlTvz2X8GKeUZTqjvZWlefmKQvQfKYOJhQo90Yjhpcr8aclg==", + "dev": true, + "requires": { + "inspect-with-kind": "^1.0.4", + "npm-cli-dir": "^3.0.0" + } + }, + "sade": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", + "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } + }, + "totalist": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", + "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "uvu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", + "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", + "dev": true, + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3", + "totalist": "^2.0.0" + } + }, + "watchexec-bin": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/watchexec-bin/-/watchexec-bin-1.0.0.tgz", + "integrity": "sha512-X38+J0qwbvvxlkcyX1ql/n9GJPwEthRhtBwly66atVcaUL808GXxQkUaCqKcN6mKonIcHdN7UmQwpR1PK86gAQ==", + "dev": true, + "requires": { + "arch": "^2.1.1", + "load-from-cwd-or-npm": "^3.0.1", + "platform-name": "^1.0.0", + "reject-unsatisfied-npm-version": "^1.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "win-user-installed-npm-cli-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/win-user-installed-npm-cli-path/-/win-user-installed-npm-cli-path-3.0.1.tgz", + "integrity": "sha512-Us1ZlMmWDInXihJ+SWP8/L0ArqsLqPtWK9Q67x6+q+z7C2c22viVgCmbH+x0BeMsosmPS9OKHvka519XbO51Rw==", + "dev": true + } + } +} diff --git a/packages/helpers/package.json b/packages/helpers/package.json new file mode 100644 index 00000000..f246be31 --- /dev/null +++ b/packages/helpers/package.json @@ -0,0 +1,30 @@ +{ + "name": "helpers", + "version": "0.0.1", + "description": "", + "main": "index.js", + "directories": { + "test": "test" + }, + "type": "module", + "scripts": { + "test": "uvu test", + "test:watch": "watchexec -w test npm test" + }, + "keywords": [ + "custom-elements", + "custom-elements-json", + "custom-elements-manifest", + "customelements", + "webcomponents", + "customelementsjson", + "customelementsmanifest" + ], + "author": "", + "license": "ISC", + "devDependencies": { + "chai": "^4.3.4", + "uvu": "^0.5.1", + "watchexec-bin": "^1.0.0" + } +} diff --git a/packages/helpers/test/fixtures/classes.json b/packages/helpers/test/fixtures/classes.json new file mode 100644 index 00000000..f9676611 --- /dev/null +++ b/packages/helpers/test/fixtures/classes.json @@ -0,0 +1,55 @@ +{ + "schemaVersion": "0.1.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "fixtures/-default/package/bar.js", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "MyElement", + "superclass": { + "name": "HTMLElement" + }, + "customElement": true, + "tagName": "my-element" + }, + { + "kind": "class", + "description": "", + "name": "Foo" + }, + { + "kind": "class", + "description": "", + "name": "MyElement2", + "superclass": { + "name": "HTMLElement" + }, + "customElement": true, + "tagName": "my-element2" + } + ], + "exports": [ + { + "kind": "custom-element-definition", + "name": "my-element", + "declaration": { + "name": "MyElement", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "custom-element-definition", + "name": "my-element2", + "declaration": { + "name": "MyElement2", + "module": "fixtures/-default/package/bar.js" + } + } + ] + } + ] +} diff --git a/packages/helpers/test/fixtures/inheritance_mixins_superclass.json b/packages/helpers/test/fixtures/inheritance_mixins_superclass.json new file mode 100644 index 00000000..35e8938f --- /dev/null +++ b/packages/helpers/test/fixtures/inheritance_mixins_superclass.json @@ -0,0 +1,95 @@ +{ + "schemaVersion": "0.1.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "fixtures/-default/package/bar.js", + "declarations": [ + { + "kind": "mixin", + "description": "", + "name": "DedupeMixin", + "parameters": [ + { + "name": "klass" + } + ] + }, + { + "kind": "mixin", + "description": "", + "name": "LocalizeMixin", + "mixins": [ + { + "name": "DedupeMixin", + "module": "fixtures/-default/package/bar.js" + } + ], + "parameters": [ + { + "name": "klass" + } + ] + }, + { + "kind": "class", + "description": "", + "name": "LitElement", + "superclass": { + "name": "UpdatingElement" + } + }, + { + "kind": "class", + "description": "", + "name": "MyComponent", + "mixins": [ + { + "name": "LocalizeMixin", + "module": "fixtures/-default/package/bar.js" + } + ], + "superclass": { + "name": "LitElement" + }, + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "DedupeMixin", + "declaration": { + "name": "DedupeMixin", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "LocalizeMixin", + "declaration": { + "name": "LocalizeMixin", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "LitElement", + "declaration": { + "name": "LitElement", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "MyComponent", + "declaration": { + "name": "MyComponent", + "module": "fixtures/-default/package/bar.js" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/helpers/test/fixtures/inheritance_superclass.json b/packages/helpers/test/fixtures/inheritance_superclass.json new file mode 100644 index 00000000..8a1c230c --- /dev/null +++ b/packages/helpers/test/fixtures/inheritance_superclass.json @@ -0,0 +1,64 @@ +{ + "schemaVersion": "0.1.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "fixtures/-default/package/bar.js", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "UpdatingElement", + "superclass": { + "name": "HTMLElement" + }, + "customElement": true + }, + { + "kind": "class", + "description": "", + "name": "LitElement", + "superclass": { + "name": "UpdatingElement" + } + }, + { + "kind": "class", + "description": "", + "name": "MyComponent", + "superclass": { + "name": "LitElement" + }, + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "UpdatingElement", + "declaration": { + "name": "UpdatingElement", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "LitElement", + "declaration": { + "name": "LitElement", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "MyComponent", + "declaration": { + "name": "MyComponent", + "module": "fixtures/-default/package/bar.js" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/helpers/test/helpers.test.js b/packages/helpers/test/helpers.test.js new file mode 100644 index 00000000..25f5cb2b --- /dev/null +++ b/packages/helpers/test/helpers.test.js @@ -0,0 +1,201 @@ +import { suite } from 'uvu'; +import { expect } from 'chai'; +import { + hasModules, + isClass, + hasExports, + hasDeclarations, + isFunction, + isVariable, + hasAttributes, + hasEvents, + hasSlots, + hasMethods, + hasCssParts, + hasCssProperties, + hasFields, + hasMixins, + isField, + isMethod, +} from '../helpers.js'; + +const helpers = suite('helpers'); +const fixtureA = { + schemaVersion: '', + modules: [{ kind: 'javascript-module', path: '', declarations: [] }], +}; + +/** + * packageDoc + */ +helpers('hasModules - true', () => { + expect(hasModules(fixtureA)).to.equal(true); +}); + +helpers('hasModules - false', () => { + expect(hasModules({ ...fixtureA, modules: [] })).to.equal(false); +}); + +/** + * moduleDoc + */ +const fixtureB = { + kind: 'javascript-module', + path: '', + declarations: [{ kind: 'class', name: '' }], + exports: [{ kind: 'js', name: '', declaration: { name: '' } }], +}; + +helpers('hasExports - true', () => { + expect(hasExports(fixtureB)).to.equal(true); +}); + +helpers('hasExports - false', () => { + expect(hasExports({ ...fixtureB, exports: [] })).to.equal(false); +}); + +helpers('hasDeclarations - true', () => { + expect(hasDeclarations(fixtureB)).to.equal(true); +}); + +helpers('hasDeclarations - false', () => { + expect(hasDeclarations({ ...fixtureB, declarations: [] })).to.equal(false); +}); + + +/** + * isClass + */ +const fixtureC = { kind: 'class', name: '' }; + +helpers('isClass - true', () => { + expect(isClass(fixtureC)).to.equal(true); +}); + +helpers('isClass - false', () => { + expect(isClass({ ...fixtureC, kind: 'function' })).to.equal(false); +}); + +/** + * isFunction + */ +const fixtureD = { kind: 'function', name: '' }; + +helpers('isFunction - true', () => { + expect(isFunction(fixtureD)).to.equal(true); +}); + +helpers('isFunction - false', () => { + expect(isFunction({ ...fixtureD, kind: 'class' })).to.equal(false); +}); + +/** + * isVariable + */ +const fixtureE = { kind: 'variable', name: '' }; + +helpers('isVariable - true', () => { + expect(isVariable(fixtureE)).to.equal(true); +}); + +helpers('isVariable - false', () => { + expect(isVariable({ ...fixtureE, kind: 'class' })).to.equal(false); +}); + +/** + * CustomElementDoc + */ +const fixtureF = { + tagName: '', + name: '', + attributes: [{ name: 'foo' }], + events: [{ name: 'foo', description: '', type: { text: '' } }], + slots: [{ name: '' }], + cssParts: [{}], + cssProperties: [{}], + members: [ + { kind: 'field', name: '' }, + { kind: 'method', name: '' }, + ], + mixins: [{ name: '' }], +}; + +helpers('hasAttributes - true', () => { + expect(hasAttributes(fixtureF)).to.equal(true); +}); + +helpers('hasAttributes - false', () => { + expect(hasAttributes({ ...fixtureF, attributes: [] })).to.equal(false); +}); + +helpers('hasEvents - true', () => { + expect(hasEvents(fixtureF)).to.equal(true); +}); + +helpers('hasEvents - false', () => { + expect(hasEvents({ ...fixtureF, events: [] })).to.equal(false); +}); + +helpers('hasCssParts - true', () => { + expect(hasCssParts(fixtureF)).to.equal(true); +}); + +helpers('hasCssProperties - true', () => { + expect(hasCssProperties(fixtureF)).to.equal(true); +}); + +helpers('hasSlots - true', () => { + expect(hasSlots(fixtureF)).to.equal(true); +}); + +helpers('hasSlots - false', () => { + expect(hasSlots({ ...fixtureF, slots: [] })).to.equal(false); +}); + +helpers('hasFields - true', () => { + expect(hasFields({ ...fixtureF, members: [{ kind: 'field', name: '' }] })).to.equal(true); +}); + +helpers('hasFields - false', () => { + expect(hasFields({ ...fixtureF, members: [] })).to.equal(false); +}); + +helpers('hasMethods - true', () => { + expect(hasMethods({ ...fixtureF, members: [{ kind: 'method', name: '' }] })).to.equal(true); +}); + +helpers('hasMethods - false', () => { + expect(hasMethods({ ...fixtureF, members: [] })).to.equal(false); +}); + +helpers('hasMixins - true', () => { + expect(hasMixins(fixtureF)).to.equal(true); +}); + +helpers('hasMixins - false', () => { + expect(hasMixins({ ...fixtureF, mixins: [] })).to.equal(false); +}); + +const fixtureG = { kind: 'field', name: '' }; + +helpers('isField - true', () => { + expect(isField(fixtureG)).to.equal(true); +}); + +helpers('isField - false', () => { + expect(isField({ ...fixtureG, kind: 'method' })).to.equal(false); +}); + +const fixtureH = { kind: 'method', name: '' }; + +helpers('isMethod - true', () => { + expect(isMethod(fixtureH)).to.equal(true); +}); + +helpers('isMethod - false', () => { + expect(isMethod({ ...fixtureH, kind: 'field' })).to.equal(false); +}); + +helpers.run(); + + diff --git a/packages/helpers/test/index.test.js b/packages/helpers/test/index.test.js new file mode 100644 index 00000000..703df833 --- /dev/null +++ b/packages/helpers/test/index.test.js @@ -0,0 +1,75 @@ +import fs from 'fs'; +import { test } from 'uvu'; +import { expect } from 'chai'; +import { CustomElementsJson } from '../index.js'; + + +const classes = JSON.parse(fs.readFileSync('test/fixtures/classes.json').toString()) +const inheritanceSuperclass = JSON.parse(fs.readFileSync('test/fixtures/inheritance_superclass.json').toString()) +const inheritanceMixinsSuperclass = JSON.parse(fs.readFileSync('test/fixtures/inheritance_mixins_superclass.json').toString()) + + +test('getByTagName', () => { + const customElementsJson = new CustomElementsJson(classes); + expect(customElementsJson.getByTagName('my-element').name).to.equal('MyElement'); + expect(customElementsJson.getByTagName('my-element').tagName).to.equal('my-element'); +}); + + +test('getByClassName', () => { + const customElementsJson = new CustomElementsJson(classes); + expect(customElementsJson.getByClassName('MyElement').name).to.equal('MyElement'); + expect(customElementsJson.getByClassName('MyElement').tagName).to.equal('my-element'); +}); + + + +test('getClasses - gets all classes', () => { + const customElementsJson = new CustomElementsJson(classes); + expect(customElementsJson.getClasses().length).to.equal(3); +}); + +test('getDefinitions - gets all definitions', () => { + const customElementsJson = new CustomElementsJson(classes); + expect(customElementsJson.getDefinitions().length).to.equal(2); +}); + +test('getCustomElements - gets all custom elements', () => { + const customElementsJson = new CustomElementsJson(classes); + expect(customElementsJson.getCustomElements().length).to.equal(2); +}); + +test('getMixins - gets all mixins', () => { + const customElementsJson = new CustomElementsJson(inheritanceMixinsSuperclass); + expect(customElementsJson.getMixins().length).to.equal(2); +}); + + +test('getInheritanceTree - gets all superclasses', () => { + const customElementsJson = new CustomElementsJson(inheritanceSuperclass); + const result = customElementsJson.getInheritanceTree('MyComponent'); + + expect(result.length).to.equal(3); + expect(result[0].name).to.equal('MyComponent'); + expect(result[1].name).to.equal('LitElement'); + expect(result[2].name).to.equal('UpdatingElement'); +}); + +test('getInheritanceTree - gets all superclasses and mixins', () => { + const customElementsJson = new CustomElementsJson(inheritanceMixinsSuperclass); + const result = customElementsJson.getInheritanceTree('MyComponent'); + + expect(result.length).to.equal(4); + expect(result[0].name).to.equal('MyComponent'); + expect(result[1].name).to.equal('LocalizeMixin'); + expect(result[2].name).to.equal('DedupeMixin'); + expect(result[3].name).to.equal('LitElement'); +}); + +test('getInheritanceTree - returns empty array if class not found', () => { + const customElementsJson = new CustomElementsJson(inheritanceMixinsSuperclass); + expect(customElementsJson.getInheritanceTree('AsdfAsdf').length).to.equal(0); +}); + + +test.run(); \ No newline at end of file