@vltpkg/dss-parser
Advanced tools
| import type { Root } from 'postcss-selector-parser'; | ||
| export * from './types.ts'; | ||
| /** | ||
| * Escapes forward slashes in specific patterns matching @scoped/name paths | ||
| * This will allow usage of unescaped forward slashes necessary for scoped | ||
| * package names in the id selector. | ||
| */ | ||
| export declare const escapeScopedNamesSlashes: (query: string) => string; | ||
| export declare const escapeDots: (query: string) => string; | ||
| export declare const unescapeDots: (query: string) => string; | ||
| /** | ||
| * Parses a CSS selector string into an AST | ||
| * Handles escaping of forward slashes in specific patterns | ||
| */ | ||
| export declare const parse: (query: string) => Root; |
| import postcssSelectorParser from 'postcss-selector-parser'; | ||
| import { asSelectorNode, isCombinatorNode, isPseudoNode, isTagNode, } from "./types.js"; | ||
| export * from "./types.js"; | ||
| /** | ||
| * Escapes forward slashes in specific patterns matching @scoped/name paths | ||
| * This will allow usage of unescaped forward slashes necessary for scoped | ||
| * package names in the id selector. | ||
| */ | ||
| export const escapeScopedNamesSlashes = (query) => query.replace(/(#@(\w|-|\.)+)\//gm, (_, scope) => `${scope}\\/`); | ||
| export const escapeDots = (query) => query.replaceAll('.', '\\.'); | ||
| export const unescapeDots = (query) => query.replaceAll('\\.', '.'); | ||
| const pseudoCleanUpNeeded = new Set([ | ||
| ':published', | ||
| ':score', | ||
| ':malware', | ||
| ':severity', | ||
| ':sev', | ||
| ':squat', | ||
| ':semver', | ||
| ':v', | ||
| ]); | ||
| const hasParamsToEscape = (node) => pseudoCleanUpNeeded.has(node.value); | ||
| /** | ||
| * Parses a CSS selector string into an AST | ||
| * Handles escaping of forward slashes in specific patterns | ||
| */ | ||
| export const parse = (query) => { | ||
| const escapedQuery = escapeDots(escapeScopedNamesSlashes(query)); | ||
| const transformAst = (root) => { | ||
| root.walk((node) => { | ||
| // clean up the escaped dots | ||
| if (node.value && typeof node.value === 'string') { | ||
| node.value = unescapeDots(node.value); | ||
| } | ||
| if (isPseudoNode(node) && hasParamsToEscape(node)) { | ||
| // these are pseudo nodes that should only take strings as | ||
| // parameters, so in this preparse step we clean up anything | ||
| // that was recognized as a postcss node and transform that | ||
| // into something that can be most likely parsed as a string | ||
| for (const n of node.nodes) { | ||
| // the parameters have a selector node that wraps them up | ||
| const selector = asSelectorNode(n); | ||
| selector.nodes.forEach((currentNode, index, arr) => { | ||
| // get the next node, we'll update it later | ||
| const nextNode = arr[index + 1]; | ||
| // if the current node is a combinator node, we'll need to | ||
| // escape it, we do so by removing the node entirely and | ||
| // updating the contents of the next node with its value | ||
| if (isCombinatorNode(currentNode) && | ||
| isTagNode(nextNode)) { | ||
| nextNode.value = `${currentNode.spaces.before}${currentNode.value}${currentNode.spaces.after}${nextNode.value}`; | ||
| // make sure to also update the source position | ||
| // references, those are used by the syntax highlighter | ||
| if (nextNode.source?.start?.line && | ||
| currentNode.source?.start?.line) { | ||
| nextNode.source.start.line = | ||
| currentNode.source.start.line; | ||
| } | ||
| if (nextNode.source?.start?.column && | ||
| currentNode.source?.start?.column) { | ||
| nextNode.source.start.column = | ||
| currentNode.source.start.column; | ||
| } | ||
| // removes the current node from the selector node | ||
| arr.splice(index, 1); | ||
| } | ||
| }); | ||
| // after removing combinator nodes, if we end up with multiple | ||
| // tags in the selector node, we need to smush them together | ||
| selector.nodes.reduce((acc, currentNode) => { | ||
| if (currentNode === acc) | ||
| return acc; | ||
| acc.value = `${acc.value}${currentNode.spaces.before}${currentNode.value}${currentNode.spaces.after}`; | ||
| // make sure to also update the source position refs | ||
| if (currentNode.source?.end?.line && | ||
| acc.source?.end?.line) { | ||
| acc.source.end.line = currentNode.source.end.line; | ||
| } | ||
| if (currentNode.source?.end?.column && | ||
| acc.source?.end?.column) { | ||
| acc.source.end.column = currentNode.source.end.column; | ||
| } | ||
| return acc; | ||
| }, selector.first); | ||
| // the selector wrapper node should have a single node | ||
| selector.nodes.length = 1; | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| return postcssSelectorParser(transformAst).astSync(escapedQuery); | ||
| }; |
| import type { Tag, String, Selector, Root, Pseudo, Nesting, Identifier, Comment, Combinator, ClassName, Attribute, Universal, tag, id, combinator, string, attribute, pseudo } from 'postcss-selector-parser'; | ||
| export type PostcssNode = Tag | String | Selector | Root | Pseudo | Nesting | Identifier | Comment | Combinator | ClassName | Attribute | Universal; | ||
| export type PostCSSLeaf = ReturnType<typeof tag> | ReturnType<typeof id> | ReturnType<typeof attribute> | ReturnType<typeof combinator> | ReturnType<typeof pseudo> | ReturnType<typeof string>; | ||
| export type PostcssNodeWithChildren = Selector | Root | Pseudo; | ||
| export type ParsedSelectorToken = PostcssNode & { | ||
| token: string; | ||
| }; | ||
| export declare const isPostcssNodeWithChildren: (node: any) => node is PostcssNodeWithChildren; | ||
| export declare const asPostcssNodeWithChildren: (node?: PostcssNode) => PostcssNodeWithChildren; | ||
| export declare const isAttributeNode: (node: unknown) => node is Attribute; | ||
| export declare const asAttributeNode: (node?: PostcssNode) => Attribute; | ||
| export declare const isCombinatorNode: (node: unknown) => node is Combinator; | ||
| export declare const asCombinatorNode: (node?: PostcssNode) => Combinator; | ||
| export declare const isIdentifierNode: (node: any) => node is Identifier; | ||
| export declare const asIdentifierNode: (node?: PostcssNode) => Identifier; | ||
| export declare const isSelectorNode: (node: any) => node is Selector; | ||
| export declare const asSelectorNode: (node?: PostcssNode) => Selector; | ||
| export declare const isPseudoNode: (node: unknown) => node is Pseudo; | ||
| export declare const asPseudoNode: (node?: PostcssNode) => Pseudo; | ||
| export declare const isTagNode: (node: unknown) => node is Tag; | ||
| export declare const asTagNode: (node?: PostcssNode) => Tag; | ||
| export declare const isStringNode: (node: unknown) => node is String; | ||
| export declare const asStringNode: (node?: PostcssNode) => String; | ||
| export declare const isCommentNode: (node: unknown) => node is Comment; | ||
| export declare const asCommentNode: (node?: PostcssNode) => Comment; |
+118
| import { error } from '@vltpkg/error-cause'; | ||
| export const isPostcssNodeWithChildren = (node) => 'type' in node && 'nodes' in node; | ||
| export const asPostcssNodeWithChildren = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isPostcssNodeWithChildren(node)) { | ||
| throw error('Not a query selector node with children', { | ||
| found: node, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| const isObj = (o) => !!o && typeof o === 'object'; | ||
| export const isAttributeNode = (node) => isObj(node) && !!node.attribute && node.type === 'attribute'; | ||
| export const asAttributeNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isAttributeNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'attribute', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isCombinatorNode = (node) => isObj(node) && !!node.value && node.type === 'combinator'; | ||
| export const asCombinatorNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isCombinatorNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'combinator', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isIdentifierNode = (node) => isObj(node) && !!node.value && node.type === 'id'; | ||
| export const asIdentifierNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isIdentifierNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'id', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isSelectorNode = (node) => isPostcssNodeWithChildren(node) && node.type === 'selector'; | ||
| export const asSelectorNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isSelectorNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'selector', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isPseudoNode = (node) => isObj(node) && !!node.value && node.type === 'pseudo'; | ||
| export const asPseudoNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isPseudoNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'pseudo', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isTagNode = (node) => isObj(node) && !!node.value && node.type === 'tag'; | ||
| export const asTagNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isTagNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'tag', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isStringNode = (node) => isObj(node) && !!node.value && node.type === 'string'; | ||
| export const asStringNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isStringNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'string', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; | ||
| export const isCommentNode = (node) => isObj(node) && !!node.value && node.type === 'comment'; | ||
| export const asCommentNode = (node) => { | ||
| if (!node) { | ||
| throw error('Expected a query node'); | ||
| } | ||
| if (!isCommentNode(node)) { | ||
| throw error('Mismatching query node', { | ||
| wanted: 'comment', | ||
| found: node.type, | ||
| }); | ||
| } | ||
| return node; | ||
| }; |
+2
-2
| { | ||
| "name": "@vltpkg/dss-parser", | ||
| "description": "The Dependency Selector Syntax (DSS) parser", | ||
| "version": "1.0.0-rc.23", | ||
| "version": "1.0.0-rc.24", | ||
| "repository": { | ||
@@ -15,3 +15,3 @@ "type": "git", | ||
| "dependencies": { | ||
| "@vltpkg/error-cause": "1.0.0-rc.23", | ||
| "@vltpkg/error-cause": "1.0.0-rc.24", | ||
| "postcss-selector-parser": "^7.1.1" | ||
@@ -18,0 +18,0 @@ }, |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 2 instances in 1 package
15429
246.88%7
133.33%250
Infinity%1
-66.67%+ Added
- Removed