AST - Find if object keys have usage - babeljs

Very very inexperienced with AST parsing, been at it for a day making all rite progress got it working on a single file, but not through imports.
The idea is that i want to tag an object with a comment to flag it for extremely aggressive tree shaking.
In
// (object-method.js)
export const ObjectMethods = /*#__PURE__*/ {
foo: () => "do foo",
bar: () => "do bar"
}
// index.js
import { ObjectMethods } from "./object-methods";
export const runActions = () => {
ObjectMethods .bar();
};
out (foo is deleted as object key because its never used)
// (object-method.js)
export const ObjectMethods = /*#__PURE__*/ {
bar: () => "do bar"
}
// index.js
import { ObjectMethods } from "./object-methods";
export const runActions = () => {
ObjectMethods .bar();
};
Its basically a glorified "usage" detector, just has to be real basic only support objects declared at top-level scope with the comment.
This is what iv'e got up to but then this only works on usage within a single file....
const PURE_IDENTIFIER = "#__PURE__";
export default function() {
return {
visitor: {
ObjectExpression(program) {
if(!program.node.leadingComments) return;
if(program.node.leadingComments.some((comment) => comment.value === PURE_IDENTIFIER)) {
let objectKeys = program.node.properties.map((prop) => prop.key.loc.identifierName);
let found = [];
program.parentPath.parentPath.parentPath.parentPath.traverse({
Identifier(p) {
if(objectKeys.includes(p.node.loc.identifierName)) {
found.push(p.node.loc.identifierName);
}
}
});
let duplicates = found.filter((item, index) => found.indexOf(item) === index && found.lastIndexOf(item) !== index);
program.node.properties = program.node.properties.filter((prop) => {
/* REMOVES DUPLICATES */
return duplicates.includes(prop.key.loc.identifierName)
});
}
},
},
};
}
The question is how can i support multifile usage detection and removal - Thanks

Related

How to insert a draft-js custom component/block

I'm trying to insert my custom block to the editorState of draft-js's editor. I can't seem to find any detailed information on how to accomplish this.
Block Renderer:
const blockRendererFn = (contentBlock) => {
const type = contentBlock.getType();
if (type === 'CustomTestChipBlock') {
return {
component: CustomTestChipBlock,
editable: false,
props: {
foo: 'bar',
},
};
}
}
Block Render Map:
import { DefaultDraftBlockRenderMap } from "draft-js";
import { Map } from 'immutable';
const blockRenderMap = Map({
CustomTestChipBlock: {
element: 'div',
}
}).merge(DefaultDraftBlockRenderMap);
My custom block (material ui chip):
import { Chip } from "#mui/material";
const CustomTestChipBlock = (props) => {
const { block, contentState } = props;
const { foo } = props.blockProps;
const data = contentState.getEntity(block.getEntityAt(0)).getData();
console.log("foo: "+foo)
console.log("data: "+data)
return (
<Chip label="test" size="small"/>
)
}
Now my problem is when I try to insert my custom block. I assume my method of insertion must be wrong. I tried multiple insertion methods but due to lack of any detailed information on the subject, all of them ended up not even running the console.log inside my custom component.
Insertion:
const addChip = () => {
setEditorState(insertBlock("CustomTestChipBlock"));
}
const insertBlock = (type) => {
// This is where I can't find any detailed info at all
const newBlock = new ContentBlock({
key: genKey(),
type: type,
text: "",
characterList: List(),
});
const contentState = editorState.getCurrentContent();
const newBlockMap = contentState.getBlockMap().set(newBlock.key, newBlock);
const newEditorState = ContentState.createFromBlockArray(
newBlockMap.toArray()
)
.set("selectionBefore", contentState.getSelectionBefore())
.set("selectionAfter", contentState.getSelectionAfter());
return EditorState.push(editorState, newEditorState, "add-chip");
};

Unable to parse class method decorator in a Babel plugin

I'm writing a Babel plugin that manipulates the AST node related to a specific decorator. I'm traversing the AST but for some reason, my plugin doesn't detect the method decorator - node.decorators is always null when the visitor visits a node.
This is the plugin:
import { ClassMethod, Decorator } from '#babel/types';
import { get } from 'lodash';
import { NodePath, PluginObj } from '#babel/core';
const providerArgumentsTransformer = (): PluginObj => ({
visitor: {
ClassMethod({ node, parent }: NodePath<ClassMethod>) {
const decorator = getProviderDecorator(node.decorators); // <- node.decorators is always null
if (getDecoratorName(decorator) === 'Provides') {
console.log('Success');
}
},
},
});
function getProviderDecorator(decorators: Array<Decorator> | undefined | null): Decorator | undefined {
return decorators?.find((decorator) => get(decorator, 'expression.callee.name') === 'Provides');
}
function getDecoratorName(decorator?: Decorator): string | undefined {
return get(decorator, 'expression.callee.name');
}
export default providerArgumentsTransformer;
I'm testing the decorator as follows:
import { PluginObj } from '#babel/core';
import * as babel from '#babel/core';
import providerArgumentsTransformer from './providerArgumentsTransformer';
const code = `class MainGraph {
Provides(clazz, propertyKey, descriptor) { }
#Provides()
someString(stringProvider) {
return stringProvider.theString;
}
}`;
describe('Provider Arguments Transformer', () => {
const uut: PluginObj = providerArgumentsTransformer();
it('Exposes transformer', () => {
babel.transformSync(code, {
plugins: [
['#babel/plugin-proposal-decorators', { legacy: true }],
['#babel/plugin-proposal-class-properties', { legacy: true }],
[uut, { legacy: true }],
],
configFile: false,
});
});
});
I wonder if the issue is related to how babel.transformSync is used or perhaps the visitor is not configured properly.
Turns out the decorators were missing because #babel/plugin-proposal-decorators clears the decorators when it traverses the AST.
In order to visit the node before #babel/plugin-proposal-decorators I had to modify my visitor a bit. This approach should probably be optimized by visiting ClassBody or ClassExpression instead of Program.
const providerArgumentsTransformer: PluginObj = {
visitor: {
Program(path: NodePath<Program>) {
path.traverse(internalVisitor);
},
},
};
const internalVisitor = {
ClassMethod: {
enter({ node }: NodePath<ClassMethod>) {
// node.decorators are not null anymore
},
},
};

Rendering a menu in vue 3 after ajax method

I've gotten this menu to work without filtering it, but now I'm doing an ajax request to filter out menu items the user isn't supposed to see, and I'm having some trouble to figure out how to set the resulting menu data, the line that is not working is commented below:
<script>
import { ref } from 'vue';
import axios from 'axios';
var currentSelected = 'device_access';
var menuData = [
{
text: 'Device Access',
id: 'device_access',
children: [
{
text: 'Interactive',
link: '/connection_center'
},{
text: 'Reservation',
link: '/reserve_probe'
}, {
text: 'Reservation Vue',
link: '/reservation.html'
}
]
}, {
text: 'Automation',
id: 'automation',
show: ['is_mxadmin', 'can_schedule_scripts'],
children: [
{
text: 'Builder',
link: '/builder',
},{
text: 'Execution Results',
link: '/test_suite_execution_results'
},
]
}
];
function hasMatch(props, list) {
var match = false;
for (var i=0; i < list.length && !match; i++) {
match = props[list[i]];
}
return match;
}
export default {
name: 'Header',
setup() {
const cursorPosition = ref('0px');
const cursorWidth = ref('0px');
const cursorVisible = ref('visible');
//the menu is zero length until I get the data:
const menu = ref([]);
return {
menu,
cursorPosition,
cursorWidth,
cursorVisible
}
},
created() {
let that = this;
axios.get('navigation_props')
.then(function(res) {
var data = res.data;
var result = [];
menuData.forEach(function(item) {
if (!item.show || hasMatch(data, item.show)) {
var children = [];
item.children.forEach(function (child) {
if (!child.show || hasMatch(data, child.show)) {
children.push({ text: child.text, link: child.link });
}
});
if (children.length > 0) {
result.push({ text: item.text,
children: children, lengthClass: "length_" + children.length });
}
}
});
//continues after comment
this is probably the only thing wrong, I've run this in the debugger and I'm getting the
correct data:
that.$refs.menu = result;
since the menu is not being rebuilt, then this fails:
//this.restoreCursor();
})
.catch(error => {
console.log(error)
// Manage errors if found any
});
},
this.$refs is for template refs, which are not the same as the refs from setup().
And the data fetching in created() should probably be moved to onMounted() in setup(), where the axios.get() callback sets menu.value with the results:
import { onMounted, ref } from 'vue'
export default {
setup() {
const menu = ref([])
onMounted(() => {
axios.get(/*...*/).then(res => {
const results = /* massage res.data */
menu.value = results
})
})
return {
menu
}
}
}
I finally figured out the problem.
This code above will probably work with:
that.menu = result;
You don't need: that.$refs.menu
You can't do it in setup because for some reason "that" is not yet defined.
In my working code I added a new method:
methods: {
setMenuData: function() {
this.menu = filterMenu();
},
}
And "this" is properly defined inside them.

Draft-JS - Entity component doesn't re-render when data changes

I would like to have my text editor display how many times a content block has been selected and executed though a key command.
I'm doing this by applying an entity to the selected block with a evaluatedTimes property.
The data is changed correctly but the entity component doesn't re-render until I insert new characters in the block of text.
The decorator strategy of the entity doesn't get called, so the only way out I found is to update the editor state with a new decorator instance. This way the entity components get re-rendered but the solution fills a bit hacky.
dependencies:
"draft-js": "^0.11.3",
"draft-js-plugins-editor": "^3.0.0",
"draftjs-utils": "^0.10.2"
code:
// plugin.js
import { EditorState } from "draft-js";
import { getSelectionText, getSelectionEntity } from "draftjs-utils";
import { EvaluatedSpan } from "./components";
import { findEvaluatedEntities, createEvaluatedEntity } from "./entities";
export function createCodeEvaluationPlugin({ onEvaluate = () => {} }) {
return {
decorators: [
{
strategy: findEvaluatedEntities,
component: EvaluatedSpan
}
],
keyBindingFn: e => {
// CMD + ENTER
if (e.metaKey && e.keyCode === 13) {
return "evaluate";
}
},
handleKeyCommand: (command, editorState, _, { setEditorState }) => {
if (command === "evaluate") {
const selectionState = editorState.getSelection();
const contentState = editorState.getCurrentContent();
const entityKey = getSelectionEntity(editorState);
// If selection contains an entity:
if (!entityKey) {
// Create new entity and update editor.
setEditorState(createEvaluatedEntity(editorState, selectionState));
} else {
// Modify entity data.
const entity = contentState.getEntity(entityKey);
const nextContentState = contentState.mergeEntityData(entityKey, {
evaluatedTimes: entity.data.evaluatedTimes + 1
});
// Update editor.
setEditorState(
EditorState.push(editorState, nextContentState, "change-block-data")
);
}
// Pass text to callback handle
const selectionText = getSelectionText(editorState);
onEvaluate(selectionText);
return "handled";
}
return "not-handled";
}
};
}
According to this (https://github.com/facebook/draft-js/issues/1702) you could also update the selection state, which may be sufficient for you.
Works for me:
function forceRerender(editorState: EditorState): EditorState {
const selectionState = editorState.getSelection();
return EditorState.forceSelection(editorState, selectionState);
}

Why TextDocumentContentProvider dont call provideTextDocumentContent on update when query params changes?

as title says, when i wanna update TextDocumentContentProvider with different query params by calling update method provideTextDocumentContent is not called...
only way i managed to get it working was with same URI as in calling
vscode.commands.executeCommand('vscode.previewHtml', URI, 2, 'Storybook');
relevant part of code:
// calculates uri based on editor state - depends on actual caret position
// all uris will start with 'storybook://preview'
function getPreviewUri(editor: vscode.TextEditor): vscode.Uri;
// transforms uri, so web server will understand
// ex: 'storybook://preview?name=fred' -> 'http://localhost:12345/preview/fred?full=1'
function transformUri(uri: vscode.Uri): vscode.Uri;
class StorybookContentProvider implements vscode.TextDocumentContentProvider
{
provideTextDocumentContent(uri: vscode.Uri): string {
var httpUri = transformUri(uri);
return `<iframe src="${httpUri}" />`;
}
onDidChange = new vscode.EventEmitter<vscode.Uri>();
update(uri: vscode.Uri) {
this.onDidChange(uri);
}
}
export function activate(context: vscode.ExtensionContext)
{
vscode.workspace.onDidChangeTextDocument(
(e: vscode.TextDocumentChangeEvent) => {
if (e.document === vscode.window.activeTextEditor.document) {
const previewUri = getPreviewUri(vscode.window.activeTextEditor);
provider.update(previewUri);
}
}
);
vscode.window.onDidChangeTextEditorSelection(
(e: vscode.TextEditorSelectionChangeEvent) => {
if (e.textEditor === vscode.window.activeTextEditor) {
const previewUri = getPreviewUri(vscode.window.activeTextEditor);
provider.update(previewUri);
}
}
);
const provider = new StorybookContentProvider();
context.subscriptions.push(
vscode.commands.registerCommand('extension.showStorybook', () => {
vscode.commands.executeCommand('vscode.previewHtml', vscode.Uri.parse('storybook://preview'), 2, 'Storybook')
}),
vscode.workspace.registerTextDocumentContentProvider('storybook', provider)
);
}