generated from MengLinMaker/npm-library-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: create preprocessed data (#55)
- Loading branch information
1 parent
dd746d8
commit 5e6a53d
Showing
17 changed files
with
488 additions
and
699 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# Data | ||
preprocessCompatData.json | ||
|
||
# Test | ||
coverage | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,6 @@ | ||
{ | ||
"$schema": "https://unpkg.com/knip@5/schema.json", | ||
"ignore": ["src/*.ts", "**/tests/setup/file.ts"], | ||
"ignore": ["**/tests/setup/file.ts"], | ||
"ignoreBinaries": ["only-allow"], | ||
"ignoreDependencies": [ | ||
"@changesets/changelog-github", | ||
"@commitlint/cli", | ||
"@commitlint/config-conventional", | ||
"cz-git", | ||
"@typescript-eslint/parser" | ||
] | ||
"ignoreDependencies": ["@changesets/changelog-github", "@typescript-eslint/parser"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,29 +3,24 @@ | |
"scripts": { | ||
"preinstall": "npx only-allow pnpm", | ||
"postinstall": "simple-git-hooks", | ||
"commit": "czg", | ||
"format": "biome check --write --verbose", | ||
"lint": "tsc --noEmit --incremental", | ||
"test": "vitest", | ||
"build": "turbo build", | ||
"clean": "turbo clean && rm -rf node_modules", | ||
"clean": "rimraf packages/**/dist & rimraf .turbo packages/**/.turbo & rimraf node_modules packages/**/node_modules", | ||
"version": "changeset version", | ||
"release": "changeset publish", | ||
"knip": "knip" | ||
}, | ||
"devDependencies": { | ||
"@arethetypeswrong/cli": "^0.15.4", | ||
"@arethetypeswrong/cli": "0.16.4", | ||
"@biomejs/biome": "^1.9.4", | ||
"@changesets/changelog-github": "0.5.0", | ||
"@changesets/cli": "^2.27.9", | ||
"@commitlint/cli": "19.5.0", | ||
"@commitlint/config-conventional": "19.4.1", | ||
"@typescript-eslint/parser": "8.13.0", | ||
"@typescript-eslint/rule-tester": "8.12.2", | ||
"cz-git": "1.10.1", | ||
"czg": "1.10.0", | ||
"knip": "5.36.3", | ||
"rimraf": "6.0.1", | ||
"simple-git-hooks": "^2.11.1", | ||
"ts-node": "10.9.2", | ||
"tsup": "^8.3.5", | ||
"turbo": "2.2.3", | ||
"typescript": "5.6.3", | ||
|
@@ -35,10 +30,5 @@ | |
"pre-commit": "pnpm run format", | ||
"pre-push": "pnpm run lint" | ||
}, | ||
"config": { | ||
"commitizen": { | ||
"path": "node_modules/cz-git" | ||
} | ||
}, | ||
"packageManager": "[email protected]" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# data - @eslint-plugin-runtime-compat/data | ||
|
||
This is an internal package that preprocesses [`runtime-compat-data`](https://github.com/unjs/runtime-compat/tree/main/packages/runtime-compat-data): | ||
- Classify API types: class, property access, globals... | ||
- Identifies essential API information. | ||
- Reduces multi-level JSON to 2 level JSON. | ||
- Provides a filter for identifying unsupported APIs with given target runtimes. | ||
- Expose minimal interface. | ||
|
||
## Auto update | ||
|
||
Building this package with `pnpm build` will: | ||
1. Automatically update [`runtime-compat-data`](https://github.com/unjs/runtime-compat/tree/main/packages/runtime-compat-data)/. | ||
2. Run preprocessing script to produce `preprocessCompatData.json`. | ||
3. Finally build package. | ||
|
||
## Runtime usage | ||
|
||
Install in `packages.json` locally: | ||
```Json | ||
"@eslint-plugin-runtime-compat/data": "workspace:*" | ||
``` | ||
|
||
Filter for targeted runtimes: | ||
```TypeScript | ||
import { type RuntimeName, filterPreprocessCompatData, preprocessCompatData } from '@eslint-plugin-runtime-compat/data' | ||
|
||
const filterRuntimes: RuntimeName[] = ['node'] | ||
|
||
const runtimeCompatData = filterPreprocessCompatData(preprocessCompatData, filterRuntimes) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { writeFileSync } from 'node:fs' | ||
import type { CompatStatement, Identifier, StatusBlock } from 'runtime-compat-data' | ||
import rawCompatData from 'runtime-compat-data' | ||
import type { PreprocessCompatData, PreprocessCompatStatement } from './src/types' | ||
|
||
/** | ||
* Compress raw compat data to single level flatmap | ||
*/ | ||
const mapCompatData = new Map<string, PreprocessCompatStatement>() | ||
{ | ||
const objectKeys = <T extends object>(object: T) => Object.keys(object) as (keyof T)[] | ||
|
||
/** | ||
* Simplifies compat data to only relevant info | ||
* @param compatStatement The raw compat data API compat statement | ||
* @returns Preprocess compat statement for runtime filtering before linting | ||
*/ | ||
const extractPreprocessCompatStatement = ( | ||
compatStatement: CompatStatement, | ||
): PreprocessCompatStatement => { | ||
// Prefer MDN url | ||
let url = compatStatement.mdn_url | ||
if (url === undefined) { | ||
if (Array.isArray(compatStatement.spec_url)) url = compatStatement.spec_url[0] | ||
else url = compatStatement.spec_url | ||
} | ||
// Assume standard track if there is no API status | ||
const defaultStatus = { | ||
deprecated: false, | ||
experimental: false, | ||
standard_track: true, | ||
} satisfies StatusBlock | ||
return { | ||
url: url ?? 'No url provided.', | ||
status: compatStatement.status ?? defaultStatus, | ||
support: compatStatement.support, | ||
} | ||
} | ||
|
||
/** | ||
* DFS parse raw compat data | ||
* @param compatData Raw compat data and inner data | ||
* @param parentKeys Chain of keys with '__compat' as parameter | ||
*/ | ||
const parseRawCompatData = (compatData: Identifier, parentKeys: string[] = []) => { | ||
const keys = objectKeys(compatData) as string[] | ||
for (const key of keys) { | ||
const subData = compatData[key] | ||
if (key === '__compat') { | ||
const finalCompatStatement = extractPreprocessCompatStatement(subData as never) | ||
mapCompatData.set(JSON.stringify(parentKeys), finalCompatStatement) | ||
} else { | ||
// Only chain keys if "__compat" exists | ||
const nodeHasCompatData = !keys.includes('__compat') | ||
const filteredParentKeys = nodeHasCompatData ? [key] : [...parentKeys, key] | ||
if (subData) parseRawCompatData(subData, filteredParentKeys) | ||
} | ||
} | ||
} | ||
parseRawCompatData(rawCompatData.api as never) | ||
} | ||
|
||
/** | ||
* Sort mapped compat data into different AST detection scenarios | ||
*/ | ||
const preprocessCompatData: PreprocessCompatData = { | ||
class: {}, | ||
classProperty: {}, | ||
eventListener: {}, | ||
global: {}, | ||
globalClassProperty: {}, | ||
misc: {}, | ||
} | ||
{ | ||
const isPascalCase = (s: string | undefined) => s?.match(/^[A-Z]+.*/) | ||
for (const [jsonKeys, finalCompatStatement] of mapCompatData.entries()) { | ||
const keys = JSON.parse(jsonKeys) as string[] | ||
if (keys.length === 1) { | ||
if (isPascalCase(keys[0])) { | ||
// PascalCase, hence a class | ||
preprocessCompatData.class[jsonKeys] = finalCompatStatement | ||
} else { | ||
// camelCase, hence a variable or function | ||
preprocessCompatData.global[jsonKeys] = finalCompatStatement | ||
} | ||
} else if (keys.length === 2) { | ||
if (keys[0] === keys[1]) | ||
// Duplicate keys are class constructors | ||
preprocessCompatData.class[JSON.stringify([keys[0]])] = finalCompatStatement | ||
else if (keys[1]?.match('_static')) { | ||
// Static methods have '_static' | ||
const newKeys = JSON.stringify([keys[0], keys[1]?.replace('_static', '')]) | ||
if (isPascalCase(keys[0])) | ||
preprocessCompatData.classProperty[newKeys] = finalCompatStatement | ||
else preprocessCompatData.globalClassProperty[newKeys] = finalCompatStatement | ||
} else if (keys[1]?.match('_event')) { | ||
// Events have '_event' | ||
const newKeys = JSON.stringify([keys[0], keys[1]?.replace('_event', '')]) | ||
preprocessCompatData.eventListener[newKeys] = finalCompatStatement | ||
} else if (!keys[1]?.match('_')) | ||
// Normal class property | ||
preprocessCompatData.classProperty[jsonKeys] = finalCompatStatement | ||
else preprocessCompatData.misc[jsonKeys] = finalCompatStatement | ||
} else { | ||
// Not sure how to analyse | ||
preprocessCompatData.misc[JSON.stringify([keys[0]])] = finalCompatStatement | ||
} | ||
} | ||
} | ||
writeFileSync( | ||
'./src/preprocessCompatData.json', | ||
`${JSON.stringify(preprocessCompatData, null, 2)}\n`, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
import type { RuntimeName } from 'runtime-compat-data' | ||
import data from 'runtime-compat-data' | ||
import { filterSupportCompatData } from './filterSupportCompatData.js' | ||
import { mapCompatData } from './mapCompatData.js' | ||
import type { ParsedCompatData, RuleConfig } from './types.js' | ||
|
||
export type { RuleConfig, ParsedCompatData, RuntimeName } | ||
export { filterSupportCompatData, mapCompatData } | ||
export { filterSupportCompatData, mapCompatData, data } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import _preprocessCompatData from './preprocessCompatData.json' | ||
|
||
import type { RuntimeName } from 'runtime-compat-data' | ||
import { objectKeys } from './objectKeys' | ||
import type { | ||
PreprocessCompatData, | ||
PreprocessCompatStatement, | ||
RuntimeCompatData, | ||
RuntimeCompatStatement, | ||
} from './types.js' | ||
|
||
const preprocessCompatData: PreprocessCompatData = _preprocessCompatData | ||
export { preprocessCompatData } | ||
|
||
/** | ||
* Extract unsupported runtimes based of filter. | ||
* @param preprocessCompatStatement | ||
* @param filterRuntimes - Runtimes to filter for lack of support detection. | ||
* @returns Array of unsupported runtimes. | ||
*/ | ||
const getUnsupportedRuntimes = ( | ||
preprocessCompatStatement: PreprocessCompatStatement, | ||
filterRuntimes: RuntimeName[], | ||
) => { | ||
const unsupportedRuntimes: RuntimeName[] = [] | ||
|
||
for (const filterRuntime of filterRuntimes) { | ||
const support = preprocessCompatStatement.support[filterRuntime] | ||
if (support === undefined) { | ||
// Runtime not found, therefore unsupported. | ||
unsupportedRuntimes.push(filterRuntime) | ||
} else if (Array.isArray(support)) { | ||
// Array format not supported by runtime-compat-data, therefore unsupported. | ||
unsupportedRuntimes.push(filterRuntime) | ||
} else if (support.version_added === false) { | ||
// Only boolean is supported in runtime-compat-data. | ||
unsupportedRuntimes.push(filterRuntime) | ||
} | ||
} | ||
return unsupportedRuntimes | ||
} | ||
|
||
/** | ||
* Clean flat compat data object, retaining only unsupported runtimes. | ||
* @param flatCompatData - Flat compat data object. | ||
* @param filterRuntimes - Runtimes to filter for lack of support detection. | ||
* @returns Parsed unsupported runtime data. | ||
*/ | ||
export const filterPreprocessCompatData = ( | ||
preprocessCompatData: PreprocessCompatData, | ||
filterRuntimes: RuntimeName[], | ||
) => { | ||
const parsedCompatData: RuntimeCompatData = { | ||
class: new Map<string, RuntimeCompatStatement>(), | ||
classProperty: new Map<string, RuntimeCompatStatement>(), | ||
eventListener: new Map<string, RuntimeCompatStatement>(), | ||
global: new Map<string, RuntimeCompatStatement>(), | ||
globalClassProperty: new Map<string, RuntimeCompatStatement>(), | ||
misc: new Map<string, RuntimeCompatStatement>(), | ||
} | ||
for (const context of objectKeys(preprocessCompatData)) { | ||
for (const jsonKeys of objectKeys(preprocessCompatData[context])) { | ||
const preprocessCompatStatement = preprocessCompatData[context][jsonKeys] | ||
if (preprocessCompatStatement) { | ||
const unsupportedRuntimes = getUnsupportedRuntimes( | ||
preprocessCompatStatement, | ||
filterRuntimes, | ||
) | ||
if (unsupportedRuntimes.length > 0) { | ||
parsedCompatData[context].set(jsonKeys, { | ||
url: preprocessCompatStatement.url, | ||
status: preprocessCompatStatement.status, | ||
unsupported: unsupportedRuntimes, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
return parsedCompatData | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import type { RuntimeName } from 'runtime-compat-data' | ||
import { describe, expect, it } from 'vitest' | ||
import { objectKeys } from '../objectKeys' | ||
import { filterPreprocessCompatData, preprocessCompatData } from '../runtime' | ||
|
||
describe('filterPreprocessCompatData', () => { | ||
const filterRuntimes: RuntimeName[] = ['node'] | ||
|
||
it('should successfully filter for ', () => { | ||
const runtimeCompatData = filterPreprocessCompatData(preprocessCompatData, filterRuntimes) | ||
for (const context of objectKeys(runtimeCompatData)) { | ||
for (const [jsonKeys, runtimeCompatStatement] of runtimeCompatData[context].entries()) { | ||
const keys = JSON.parse(jsonKeys) as string[] | ||
expect(keys.length).toBeGreaterThan(0) | ||
expect(runtimeCompatStatement.unsupported).toStrictEqual(filterRuntimes) | ||
} | ||
} | ||
}) | ||
}) |
Oops, something went wrong.