diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32d1249..9fea0d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ on: jobs: test: - name: Unit tests + name: Tests runs-on: ubuntu-latest strategy: @@ -20,13 +20,20 @@ jobs: - 14.13.0 - 16 - 18 + - 20 steps: - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: "npm" - - run: npm install --ignore-scripts --no-audit - - run: npm test + - name: Install dependencies + run: npm install --ignore-scripts --no-audit + - name: Run unit tests + run: npm test + - name: Run type checks + run: npm run check + if: ${{ matrix.node-version }} == 20 diff --git a/package-lock.json b/package-lock.json index 277c373..81005ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ }, "devDependencies": { "microbundle": "^0.15.1", + "typescript": "^5.2.2", "uvu": "^0.5.6" }, "engines": { @@ -3709,6 +3710,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/microbundle/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5254,16 +5268,16 @@ "dev": true }, "node_modules/typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -7886,6 +7900,12 @@ "escape-string-regexp": { "version": "4.0.0", "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true } } }, @@ -8858,9 +8878,9 @@ "dev": true }, "typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 5af9f85..c0e108a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ }, "scripts": { "test": "uvu", - "build": "microbundle" + "build": "microbundle", + "check": "tsc --noEmit" }, "keywords": [ "projectwallace", @@ -47,6 +48,7 @@ }, "devDependencies": { "microbundle": "^0.15.1", + "typescript": "^5.2.2", "uvu": "^0.5.6" }, "mangle": { diff --git a/src/collection.js b/src/collection.js index 947795e..0e0f46d 100644 --- a/src/collection.js +++ b/src/collection.js @@ -50,11 +50,10 @@ export class Collection { * @property {number} column * @property {number} offset * @property {number} length - * - * @returns {{ total: number; totalUnique: number; uniquenessRatio: number; unique: Record; __unstable__uniqueWithLocations: Record}} */ count() { let uniqueWithLocations = new Map() + /** @type Record */ let unique = {} this._items.forEach((list, key) => { let nodes = list.map(index => ({ @@ -73,6 +72,7 @@ export class Collection { totalUnique: this._items.size, unique, uniquenessRatio: this._total === 0 ? 0 : this._items.size / this._total, + /** @type {Record} */ __unstable__uniqueWithLocations: Object.fromEntries(uniqueWithLocations), } } @@ -81,7 +81,7 @@ export class Collection { total: this._total, totalUnique: this._items.size, unique, - uniquenessRatio: this._total === 0 ? 0 : this._items.size / this._total, + uniquenessRatio: this._total === 0 ? 0 : this._items.size / this._total } } } diff --git a/src/countable-collection.js b/src/countable-collection.js index 404716a..44bc90f 100644 --- a/src/countable-collection.js +++ b/src/countable-collection.js @@ -1,7 +1,7 @@ class CountableCollection { /** @param {string[]?} initial */ - constructor(initial) { + constructor(initial = undefined) { /** @type {Map} */ this._items = new Map() this._total = 0 diff --git a/src/index.js b/src/index.js index 2ac2d5c..76b283d 100644 --- a/src/index.js +++ b/src/index.js @@ -38,7 +38,7 @@ let defaults = { * @param {string} css * @param {Options} options */ -export function analyze(css, options = {}) { +export function analyze(css, options = { useUnstableLocations: false }) { let settings = Object.assign({}, defaults, options) let useLocations = settings.useUnstableLocations === true let start = Date.now() @@ -84,7 +84,7 @@ export function analyze(css, options = {}) { // Atrules let totalAtRules = 0 - /** @type {Record}[]} */ + /** @type {Record[]}} */ let fontfaces = [] let layers = new Collection({ useLocations }) let imports = new Collection({ useLocations }) @@ -165,6 +165,7 @@ export function analyze(css, options = {}) { let atRuleName = node.name if (atRuleName === 'font-face') { + /** @type Record */ let descriptors = {} node.block.children.forEach(descriptor => { @@ -269,7 +270,7 @@ export function analyze(css, options = {}) { uniqueSelectors.add(selector) selectorComplexities.push(complexity) - uniqueSelectorComplexities.push(complexity, node.loc) + uniqueSelectorComplexities.push(String(complexity), node.loc) // #region specificity let [{ value: specificityObj }] = calculate(node) diff --git a/src/index.test.js b/src/index.test.js index 796af7d..837f3b3 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -214,7 +214,6 @@ Api('handles empty input gracefully', () => { "total": 0, "totalUnique": 0, "unique": {}, - "total": 0, "uniquenessRatio": 0, "items": [] }, diff --git a/src/rules/rules.test.js b/src/rules/rules.test.js index 63091f4..70a1c38 100644 --- a/src/rules/rules.test.js +++ b/src/rules/rules.test.js @@ -59,7 +59,6 @@ Rules('should handle CSS without rules', () => { range: 0, sum: 0, items: [], - items: [], unique: {}, total: 0, totalUnique: 0, diff --git a/src/selectors/utils.js b/src/selectors/utils.js index e8503f2..6533d4e 100644 --- a/src/selectors/utils.js +++ b/src/selectors/utils.js @@ -5,7 +5,7 @@ import { hasVendorPrefix } from '../vendor-prefix.js' /** * * @param {import('css-tree').SelectorList} selectorListAst - * @returns {Selector[]} Analyzed selectors in the selectorList + * @returns {import('css-tree').Selector[]} Analyzed selectors in the selectorList */ function analyzeList(selectorListAst, cb) { let childSelectors = [] @@ -64,7 +64,7 @@ export function isAccessibility(selector) { /** * Get the Complexity for the AST of a Selector Node - * @param {import('css-tree').Selector} ast - AST Node for a Selector + * @param {import('css-tree').Selector} selector - AST Node for a Selector * @return {[number, boolean]} - The numeric complexity of the Selector and whether it's prefixed or not */ export function getComplexity(selector) { diff --git a/src/string-utils.js b/src/string-utils.js index 137ae74..525383b 100644 --- a/src/string-utils.js +++ b/src/string-utils.js @@ -1,7 +1,7 @@ /** * Case-insensitive compare two character codes - * @param {string} referenceCode - * @param {string} testCode + * @param {number} referenceCode + * @param {number} testCode * @see https://github.com/csstree/csstree/blob/41f276e8862d8223eeaa01a3d113ab70bb13d2d9/lib/tokenizer/utils.js#L22 */ function compareChar(referenceCode, testCode) { diff --git a/src/values/destructure-font-shorthand.js b/src/values/destructure-font-shorthand.js index 02b366e..fe65cd5 100644 --- a/src/values/destructure-font-shorthand.js +++ b/src/values/destructure-font-shorthand.js @@ -82,20 +82,6 @@ export function destructure(value, stringifyNode) { return } - // If, after taking care of font-size and line-height, we still have a remaining dimension, it must be the oblique angle - if ( - node.type === 'Dimension' && - item.prev && - item.prev.data.type === TYPE_IDENTIFIER && - item.prev.data.name === 'oblique' - ) { - // put in the correct amount of whitespace between `oblique` and `` - font_style += - ''.padStart(node.loc.start.offset - item.prev.data.loc.end.offset) + - stringifyNode(node) - return - } - // any node that's a number and not previously caught by line-height or font-size is the font-weight // (oblique will not be caught here, because that's a Dimension, not a Number) if (node.type === 'Number') { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cc3efd8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,33 @@ +{ + "include": ["src/**/*"], + + "compilerOptions": { + "rootDir": "src", + + // target node v8+ (https://node.green/) + // the only missing feature is Array.prototype.values + "lib": ["es2019", "DOM", "DOM.Iterable"], + "target": "es2019", + + "module": "esnext", + "moduleResolution": "node", + + // silences wrong TS error, we don't compile, we only typecheck + "outDir": "./irrelevant/unused", + + "declaration": true, + "declarationDir": "types", + + "noEmitOnError": true, + "noErrorTruncation": true, + + "allowJs": true, + "checkJs": true, + + // TODO: error all the things + //"strict": true, + // "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + } +}