Skip to content
This repository has been archived by the owner on Jul 23, 2023. It is now read-only.

Commit

Permalink
Fixes #106 Use your project's Babel config if one is found
Browse files Browse the repository at this point in the history
If your Babel config is a JavaScript file `js-hyperclick` will show it
and ask if you trust it before executing it. If the config is a
`.babelrc`, it is just read and used.
  • Loading branch information
AsaAyers committed Dec 11, 2018
1 parent 6240dc8 commit b1082a1
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 164 deletions.
4 changes: 2 additions & 2 deletions lib/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// @flow
import parseCode from "./parse-code"
import buildSuggestion from "./build-suggestion"
import resolveModule, { hashFile } from "./resolve-module"
import resolveModule from "./resolve-module"
import findDestination from "./find-destination"

export { parseCode, buildSuggestion, resolveModule, findDestination, hashFile }
export { parseCode, buildSuggestion, resolveModule, findDestination }
82 changes: 42 additions & 40 deletions lib/core/parse-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,50 +52,52 @@ function findIdentifiers(node, identifiers = []) {
throw new Error("Unknown node type")
}

export default function parseCode(code: string): Info {
const makeDefaultConfig = () => ({
sourceType: "module",
// Enable as many plugins as I can so that people don't need to configure
// anything.
plugins: [
require("@babel/plugin-syntax-async-generators"),
require("@babel/plugin-syntax-bigint"),
require("@babel/plugin-syntax-class-properties"),
[
require("@babel/plugin-syntax-decorators"),
{ decoratorsBeforeExport: false },
],
require("@babel/plugin-syntax-do-expressions"),
require("@babel/plugin-syntax-dynamic-import"),
require("@babel/plugin-syntax-export-default-from"),
require("@babel/plugin-syntax-export-namespace-from"),
require("@babel/plugin-syntax-flow"),
require("@babel/plugin-syntax-function-bind"),
require("@babel/plugin-syntax-function-sent"),
require("@babel/plugin-syntax-import-meta"),
require("@babel/plugin-syntax-json-strings"),
require("@babel/plugin-syntax-jsx"),
require("@babel/plugin-syntax-logical-assignment-operators"),
require("@babel/plugin-syntax-nullish-coalescing-operator"),
require("@babel/plugin-syntax-numeric-separator"),
require("@babel/plugin-syntax-object-rest-spread"),
require("@babel/plugin-syntax-optional-catch-binding"),
require("@babel/plugin-syntax-optional-chaining"),
[
require("@babel/plugin-syntax-pipeline-operator"),
{ proposal: "minimal" },
],
require("@babel/plugin-syntax-throw-expressions"),
// Even though Babel can parse typescript, I can't have it and flow
// enabled at the same time.
// "@babel/plugin-syntax-typescript",
],
})

export default function parseCode(code: string, babelConfig: ?Object): Info {
const { traverse, types: t } = require("@babel/core")
const { parse } = require("@babel/core")
const { parseSync } = require("@babel/core")
let ast = undefined

try {
ast = parse(code, {
sourceType: "module",
// Enable as many plugins as I can so that people don't need to configure
// anything.
plugins: [
require("@babel/plugin-syntax-async-generators"),
require("@babel/plugin-syntax-bigint"),
require("@babel/plugin-syntax-class-properties"),
[
require("@babel/plugin-syntax-decorators"),
{ decoratorsBeforeExport: false },
],
require("@babel/plugin-syntax-do-expressions"),
require("@babel/plugin-syntax-dynamic-import"),
require("@babel/plugin-syntax-export-default-from"),
require("@babel/plugin-syntax-export-namespace-from"),
require("@babel/plugin-syntax-flow"),
require("@babel/plugin-syntax-function-bind"),
require("@babel/plugin-syntax-function-sent"),
require("@babel/plugin-syntax-import-meta"),
require("@babel/plugin-syntax-json-strings"),
require("@babel/plugin-syntax-jsx"),
require("@babel/plugin-syntax-logical-assignment-operators"),
require("@babel/plugin-syntax-nullish-coalescing-operator"),
require("@babel/plugin-syntax-numeric-separator"),
require("@babel/plugin-syntax-object-rest-spread"),
require("@babel/plugin-syntax-optional-catch-binding"),
require("@babel/plugin-syntax-optional-chaining"),
[
require("@babel/plugin-syntax-pipeline-operator"),
{ proposal: "minimal" },
],
require("@babel/plugin-syntax-throw-expressions"),
// Even though Babel can parse typescript, I can't have it and flow
// enabled at the same time.
// "@babel/plugin-syntax-typescript",
],
})
ast = parseSync(code, babelConfig || makeDefaultConfig())
} catch (parseError) {
debug("parseError", parseError)
/* istanbul ignore next */
Expand Down
48 changes: 5 additions & 43 deletions lib/core/resolve-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import path from "path"
import fs from "fs"
import { sync as resolve } from "resolve"
import crypto from "crypto"
import type { Resolved } from "../types"
import makeDebug from "debug"
const debug = makeDebug("js-hyperclick:resolve-module")
Expand All @@ -12,10 +11,7 @@ const debug = makeDebug("js-hyperclick:resolve-module")
const defaultExtensions = [".js", ".json", ".node"]
type ResolveOptions = {
extensions?: typeof defaultExtensions,
trustedResolvers: Array<{
hash: string,
trusted: boolean,
}>,
requireIfTrusted: (moduleName: string) => any,
}

function findPackageJson(basedir) {
Expand Down Expand Up @@ -50,23 +46,8 @@ function loadModuleRoots(basedir) {
}
}

export const hashFile = (filename: string): string => {
const hash = crypto.createHash("sha1")
hash.setEncoding("hex")
hash.write(fs.readFileSync(filename))
hash.end()
return String(hash.read())
}

const resolverHashes = {
// [path.normalize( path.join(__dirname, '../../custom-resolver.js') )]: '0000000000000000000000000000000000000000',
}

const hasChanged = (filename, hash) =>
resolverHashes[filename] != null && resolverHashes[filename] != hash

function resolveWithCustomRoots(basedir, absoluteModule, options): Resolved {
const { extensions = defaultExtensions } = options
const { extensions = defaultExtensions, requireIfTrusted } = options
const moduleName = `./${absoluteModule}`

const roots = loadModuleRoots(basedir)
Expand All @@ -84,33 +65,14 @@ function resolveWithCustomRoots(basedir, absoluteModule, options): Resolved {

if (stats.isFile()) {
const resolver = roots[i]
const hash = hashFile(resolver)
// Originally I was going to store the filename and a hash
// (trustedResolvers[resolver][hash] = true), but using a config key
// that contains a dot causes it to get broken up
// (trustedResolvers['custom-resolver']['js'][hash] = true)
const { trusted } =
options.trustedResolvers.find(tmp => tmp.hash === hash) || {}

if (trusted == null || hasChanged(resolver, hash)) {
return {
type: "resolver",
filename: resolver,
hash,
lastHash: resolverHashes[resolver],
}
} else if (trusted === false) {
continue
}

try {
resolverHashes[resolver] = hash
// $FlowExpectError
const customResolver = require(resolver)
const customResolver = requireIfTrusted(resolver)
const filename = customResolver({
basedir,
moduleName: absoluteModule,
})
debug("filename", filename)
// it's ok for a custom resolver to jut pass on a module
if (filename == null) {
continue
Expand Down Expand Up @@ -149,7 +111,7 @@ function resolveWithCustomRoots(basedir, absoluteModule, options): Resolved {
export default function resolveModule(
filePath: string,
suggestion: { moduleName: string },
options: ResolveOptions = { trustedResolvers: [] },
options: ResolveOptions,
): Resolved {
const { extensions = defaultExtensions } = options
let { moduleName } = suggestion
Expand Down
101 changes: 40 additions & 61 deletions lib/js-hyperclick.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/*global atom */
// @flow
import type { TextEditor } from "atom"
import type { Range } from "./types"
import type { Range, Resolved } from "./types"
import createDebug from "debug"

import { CompositeDisposable } from "atom"
Expand All @@ -11,6 +11,8 @@ import shell from "shell"
import makeCache from "./make-cache"
import { buildSuggestion, findDestination, resolveModule } from "./core"
import fs from "fs"
import makeRequire from "./require-if-trusted"
import type { Require } from "./require-if-trusted"

const debug = createDebug("js-hyperclick")

Expand Down Expand Up @@ -87,61 +89,28 @@ function makeProvider(subscriptions) {
}
}

function updateTrustedResolvers(hash, value) {
const key = `js-hyperclick.trustedResolvers`
const trustedResolvers = atom.config.get(key) || []
debug("Updating trusted resolver", hash, value)
atom.config.set(key, [...trustedResolvers, { hash, trusted: value }])
}

const followSuggestionPath = (fromFile, suggestion) => {
let blockNotFoundWarning = false
const requireIfTrusted: Require<() => ?Resolved> = makeRequire(
isTrusted => {
if (isTrusted) {
followSuggestionPath(fromFile, suggestion)
}

blockNotFoundWarning = true
return () => undefined
},
)
const resolveOptions = {
extensions: atom.config.get("js-hyperclick.extensions"),
trustedResolvers: atom.config.get("js-hyperclick.trustedResolvers") || [],
requireIfTrusted,
}
debug("resolveOptions", resolveOptions)
let resolved = resolveModule(fromFile, suggestion, resolveOptions)

if (resolved.type === "resolver") {
const { filename, hash, lastHash } = resolved
const message = "js-hyperclick: Trust this custom resolver?"
let detail = `filename: ${filename}\nhash: ${resolved.hash}`

if (lastHash) {
detail += `\nprevious hash: ${lastHash}`
detail += `\nThe file has changed and atom must reload to use it.`
}

const notification = atom.notifications.addInfo(message, {
detail,
dismissable: true,
buttons: [
{
text: lastHash ? "Trust & Restart" : "Trust",
onDidClick() {
updateTrustedResolvers(hash, true)
notification.dismiss()
const resolved = resolveModule(fromFile, suggestion, resolveOptions)

if (lastHash) {
return atom.reload()
}

followSuggestionPath(fromFile, suggestion)
},
},
{
text: "Never",
onDidClick() {
updateTrustedResolvers(hash, false)
notification.dismiss()
},
},
],
})
resolved = { type: "file", filename }
}

if (resolved.type === "url") {
if (blockNotFoundWarning) {
// Do nothing
} else if (resolved.type === "url") {
if (atom.packages.isPackageLoaded("web-browser")) {
atom.workspace.open(resolved.url)
} else {
Expand Down Expand Up @@ -232,6 +201,15 @@ function makeProvider(subscriptions) {
}
}

function migrateTrustedResolvers() {
const key = `js-hyperclick.trustedResolvers`
const trustedResolvers = atom.config.get(key)
if (trustedResolvers != null) {
atom.config.set("js-hyperclick.trustedFiles", trustedResolvers)
atom.config.set(key, undefined)
}
}

module.exports = {
config: {
extensions: {
Expand Down Expand Up @@ -267,23 +245,24 @@ module.exports = {
},
// This doesn't show up in the settings. Use Edit > Config if you need to
// change this.
trustedResolvers: {
type: "array",
items: {
type: "object",
properties: {
hash: { type: "string" },
trusted: { type: "boolean" },
},
},
default: [],
},
// trustedFiles: {
// type: "array",
// items: {
// type: "object",
// properties: {
// hash: { type: "string" },
// trusted: { type: "boolean" },
// },
// },
// default: [],
// },
},
activate() {
// hyperclick is bundled into nuclide
if (!atom.packages.isPackageLoaded("hyperclick")) {
require("atom-package-deps").install("js-hyperclick")
}
migrateTrustedResolvers()
debug("activate")
this.subscriptions = new CompositeDisposable()
},
Expand Down
Loading

0 comments on commit b1082a1

Please sign in to comment.