Skip to content

Commit

Permalink
refactor: add types for acorn
Browse files Browse the repository at this point in the history
  • Loading branch information
Ffloriel committed Jul 25, 2021
1 parent e556afc commit ed52fa1
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 52 deletions.
6 changes: 4 additions & 2 deletions packages/purgecss-from-jsx/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"name": "purgecss-from-jsx",
"name": "@fullhuman/purgecss-from-jsx",
"version": "4.0.3",
"description": "JSX extractor for PurgeCSS",
"author": "Ffloriel",
"homepage": "https://github.com/FullHuman/purgecss#readme",
"license": "ISC",
"license": "MIT",
"main": "lib/purgecss-from-jsx.js",
"module": "lib/purgecss-from-jsx.esm.js",
"types": "lib/purgecss-from-jsx.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
Expand Down
80 changes: 47 additions & 33 deletions packages/purgecss-from-jsx/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,92 @@
import * as acorn from "acorn";
import jsx, {
JSXAttribute,
JSXIdentifier, JSXNamespacedName,
JSXOpeningElement,
Literal
} from "acorn-jsx";
import { extend } from "acorn-jsx-walk";
import * as walk from "acorn-walk";
import jsx from "acorn-jsx";
import {extend} from "acorn-jsx-walk";

extend(walk.base);
type NodeState = {
selectors?: string[];
text?: string;
};

extend<NodeState>(walk.base);

function purgeFromJsx(options?: acorn.Options) {
return (content: string): string[] => {
// Will be filled during walk
const state = {selectors: []};
const state: NodeState = { selectors: [] };

// Parse and walk any JSXElement
walk.recursive(
acorn.Parser.extend(jsx()).parse(content, options),
walk.recursive<NodeState>(
acorn.Parser.extend(jsx()).parse(content, options),
state,
{
JSXOpeningElement(node: any, state: any, callback) {
// JSXIdentifier | JSXMemberExpression | JSXNamespacedName
const nameState: any = {};
JSXOpeningElement(acornNode, state, callback) {
const node = acornNode as JSXOpeningElement;
const nameState: NodeState = {};

callback(node.name, nameState);
if (nameState.text) {
state.selectors.push(nameState.text);
state.selectors?.push(nameState.text);
}

for (let i = 0; i < node.attributes.length; ++i) {
callback(node.attributes[i], state);
}
},
JSXAttribute(node: any, state: any, callback) {
// Literal | JSXExpressionContainer | JSXElement | nil
JSXAttribute(acornNode, state, callback) {
const node = acornNode as JSXAttribute;

if (!node.value) {
return;
}

// JSXIdentifier | JSXNamespacedName
const nameState: any = {};
const nameState: NodeState = {};
callback(node.name, nameState);

// node.name is id or className
switch (nameState.text) {
case "id":
case "className":
{
// Get text in node.value
const valueState: any = {};
callback(node.value, valueState);
case "id":
case "className":
{
// Get text in node.value
const valueState: NodeState = {};
callback(node.value, valueState);

// node.value is not empty
if (valueState.text) {
state.selectors.push(...valueState.text.split(" "));
// node.value is not empty
if (valueState.text) {
state.selectors?.push(...valueState.text.split(" "));
}
}
}
break;
default:
break;
break;
default:
break;
}
},
JSXIdentifier(node: any, state: any) {
JSXIdentifier(acornNode, state) {
const node = acornNode as JSXIdentifier;
state.text = node.name;
},
JSXNamespacedName(node: any, state: any) {
JSXNamespacedName(acornNode, state) {
const node = acornNode as JSXNamespacedName;
state.text = node.namespace.name + ":" + node.name.name;
},
// Only handle Literal for now, not JSXExpressionContainer | JSXElement
Literal(node: any, state: any) {
Literal(acornNode, state) {
const node = acornNode as Literal;
if (typeof node.value === "string") {
state.text = node.value;
}
}
},
},
{...walk.base}
{ ...walk.base }
);

return state.selectors;
return state.selectors || [];
};
}

Expand Down
4 changes: 2 additions & 2 deletions packages/purgecss-from-tsx/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "purgecss-from-tsx",
"name": "@fullhuman/purgecss-from-tsx",
"version": "4.0.3",
"description": "TSX extractor for PurgeCSS",
"author": "Ffloriel",
Expand Down Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"acorn": "^7.4.0",
"purgecss-from-jsx": "^4.0.3",
"@fullhuman/purgecss-from-jsx": "^4.0.3",
"typescript": "^4.3.2"
}
}
2 changes: 1 addition & 1 deletion packages/purgecss-from-tsx/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import purgeFromJsx from "@fullhuman/purgecss-from-jsx";
import acorn from "acorn";
import * as ts from "typescript";
import purgeFromJsx from "purgecss-from-jsx";

function purgeFromTsx(options?: {
acornOptions?: acorn.Options,
Expand Down
2 changes: 1 addition & 1 deletion scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const packages = [
},
{
name: "purgecss-from-tsx",
external: ["acorn", "purgecss-from-jsx", "typescript"],
external: ["acorn", "@fullhuman/purgecss-from-jsx", "typescript"],
}
];

Expand Down
3 changes: 2 additions & 1 deletion types/acorn-jsx-walk.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export function extend(base: any): void;
import * as walk from "acorn-walk";
export function extend<T>(base: walk.RecursiveVisitors<T>): void;
176 changes: 169 additions & 7 deletions types/acorn-jsx.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,171 @@
import acorn from "acorn";
declare function jsx(options?: jsx.Options): (BaseParser: typeof acorn.Parser) => typeof acorn.Parser;
export declare namespace jsx {
interface Options {
allowNamespaces?: boolean;
allowNamespacedObjects?: boolean;
import { Node, Parser } from "acorn";

declare module 'acorn-jsx' {

export interface JSXAttribute extends Node {
type: 'JSXAttribute';
elements?: Expression[];
expression?: null | Expression;
name: JSXIdentifier;
value: Expression;
}

export interface JSXOpeningElement extends Node {
type: 'JSXOpeningElement';
attributes: JSXAttribute[];
name: JSXIdentifier | JSXMemberExpression | JSXNamespacedName;
selfClosing: boolean;
}

export interface JSXAttributeExpression extends Node {
type: 'JSXAttributeExpression';
argument?: Expression;
}

export interface JSXFragment {
children: JSXElement[],
end: number,
openingFragment: OpeningElement,
start: number,
type: 'JSXFragment',
}

export interface OpeningElement extends JSXElement {
attributes: JSXAttribute[];
}

export interface JSXElement extends Node {
type: 'JSXElement';
children: JSXElement[];
openingElement: OpeningElement;
name: JSXIdentifier | JSXMemberExpression;
}

export interface JSXExpressionContainer extends Node {
type: 'JSXExpressionContainer';
expression: Expression;
}

export interface JSXIdentifier extends Node {
type: 'JSXIdentifier';
name: string;
}

export interface JSXMemberExpression extends Node {
type: 'JSXMemberExpression';
object: JSXIdentifier | JSXMemberExpression;
property: JSXIdentifier | JSXMemberExpression;
}

export interface JSXSpreadAttribute extends Node {
type: 'JSXSpreadAttribute';
argument: Identifier;
}

export interface JSXText extends Node {
type: 'JSXText';
value: string;
}

export interface JSXNamespacedName extends Node {
type: 'JSXNamespacedName';
namespace: JSXIdentifier;
name: JSXIdentifier;
}

export interface ArrayExpression extends Node {
type: 'ArrayExpression';
elements: Expression[];
}

export interface BinaryExpression extends Node {
type: 'BinaryExpression';
left: Expression;
operator: string;
right: Expression;
}

export interface CallExpression extends Node {
type: 'CallExpression';
arguments: Expression[];
callee: Expression;
}

export interface ConditionalExpression extends Node {
type: 'ConditionalExpression';
alternate: Expression;
consequent: Expression;
test: Expression;
}

export interface ExpressionStatement extends Node {
type: 'ExpressionStatement';
expression: Expression;
}

export interface Identifier extends Node {
type: 'Identifier';
name: string;
}

export interface Literal extends Node {
type: 'Literal';
value: string;
}

export interface LogicalExpression extends Node {
type: 'LogicalExpression';
left: Expression;
operator: string;
right: Expression;
}

export interface MemberExpression extends Node {
type: 'MemberExpression';
computed: boolean;
name?: string;
object: Literal | MemberExpression;
property?: MemberExpression;
raw?: string;
}

export interface ObjectExpression extends Node {
type: 'ObjectExpression';
properties: [{
key: { name?: string; value?: string },
value: Expression;
}]
}

export interface TemplateElement extends Node {
type: 'TemplateElement';
value: { cooked: string };
}

export interface TemplateLiteral extends Node {
type: 'TemplateLiteral';
expressions: Expression[];
quasis: Expression[];
}

export interface UnaryExpression extends Node {
type: 'UnaryExpression';
operator: string;
argument: { value: unknown };
}

export type Expression =
JSXAttribute | JSXAttributeExpression | JSXElement | JSXExpressionContainer |
JSXSpreadAttribute | JSXFragment | JSXText | JSXNamespacedName |
ArrayExpression | BinaryExpression | CallExpression | ConditionalExpression |
ExpressionStatement | Identifier | Literal | LogicalExpression | MemberExpression |
ObjectExpression | TemplateElement | TemplateLiteral | UnaryExpression

interface PluginOptions {
allowNamespacedObjects?: boolean,
allowNamespaces?: boolean,
autoCloseVoidElements?: boolean,
}
export default function(options?: PluginOptions): (BaseParser: typeof Parser) => typeof Parser
}
export default jsx;
/* eslint-enable no-use-before-define */
5 changes: 0 additions & 5 deletions types/pug-lexer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ declare module "pug-lexer" {
| "class"
| "id";

interface Attribute {
escaped: boolean;
name: string;
val: string;
}

interface Token {
name: string;
Expand Down

0 comments on commit ed52fa1

Please sign in to comment.