diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..37edf7d5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,11 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/golang:1.10 + working_directory: /go/src/github.com/codelingo/codelingo + steps: + - checkout + - run: go get -u github.com/golang/dep/cmd/dep + - run: dep ensure -v + - run: go test $(go list ./... 2>/dev/null | grep -v /tenets/ | grep -v /vendor) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..34c69056 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,58 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Check the debug tree for other required information** + +
+Bug Type + ++
Playground + + + Create a shared playground example. ++
Tenet + + + Recreate in [the playground](https://www.codelingo.io/playground/) if possible and share a link. + ++
Query Generation + + + Recreate in [the playground](https://www.codelingo.io/playground/) if possible and share a link. + +
For extra points + + + Recreate locally with one of our [ide plugins](https://github.com/codelingo/ideplugins) + + Recreate locally with the `lingo tooling query-from-offset` command ++
GitHub PR Review hanging or failing + + + We just need the link to the pull specific pull request - no other information is necessary + +
+ +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index d424ce9b..fb4e240d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor/ .DS_Store -notes.txt \ No newline at end of file +notes.txt +*.swp diff --git a/Gopkg.lock b/Gopkg.lock index fc40e896..d2875481 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,54 +3,171 @@ [[projects]] branch = "master" + digest = "1:6da51e5ec493ad2b44cb04129e2d0a068c8fb9bd6cb5739d199573558696bb94" name = "github.com/Azure/go-ansiterm" packages = [ ".", - "winterm" + "winterm", ] + pruneopts = "UT" revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" [[projects]] + digest = "1:adbfc2db750d6fbe0c165ad801ba2d730883a3bc40bc8fe829516eb42ed17771" name = "github.com/Microsoft/go-winio" packages = ["."] + pruneopts = "UT" revision = "a6d595ae73cf27a1b8fc32930668708f45ce1c85" version = "v0.4.9" [[projects]] branch = "master" + digest = "1:3721a10686511b80c052323423f0de17a8c06d417dbdd3b392b1578432a33aae" name = "github.com/Nvveen/Gotty" packages = ["."] + pruneopts = "UT" revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" [[projects]] + branch = "master" + digest = "1:e05c7b72aeba7570b1d2f9c6dc9f0373d224b16b70fa886c504de867bffe1c2e" + name = "github.com/aead/chacha20" + packages = [ + ".", + "chacha", + ] + pruneopts = "UT" + revision = "8b13a72661dae6e9e5dea04f344f0dc95ea29547" + +[[projects]] + digest = "1:1c10bf6793afd790b0949d5384403d8d4850bf8f5f72ab6fc232277daaa5057b" + name = "github.com/aead/siphash" + packages = ["."] + pruneopts = "UT" + revision = "83563a290f60225eb120d724600b9690c3fb536f" + version = "v1.0.1" + +[[projects]] + digest = "1:705c40022f5c03bf96ffeb6477858d88565064485a513abcd0f11a0911546cb6" name = "github.com/blang/semver" packages = ["."] + pruneopts = "UT" revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f" version = "v3.5.1" [[projects]] + digest = "1:8167edb74b429be8f13e3a0172d5a694565be009c56f73b42b13996095d0d5e7" + name = "github.com/btcsuite/btcd" + packages = [ + "addrmgr", + "blockchain", + "btcec", + "btcjson", + "chaincfg", + "chaincfg/chainhash", + "connmgr", + "database", + "peer", + "rpcclient", + "txscript", + "wire", + ] + pruneopts = "UT" + revision = "f3ec13030e4e828869954472cbc51ac36bee5c1d" + version = "v0.20.1-beta" + +[[projects]] + branch = "master" + digest = "1:30d4a548e09bca4a0c77317c58e7407e2a65c15325e944f9c08a7b7992f8a59e" + name = "github.com/btcsuite/btclog" + packages = ["."] + pruneopts = "UT" + revision = "84c8d2346e9fc8c7b947e243b9c24e6df9fd206a" + +[[projects]] + digest = "1:b247ee960cb32dceecc22b825f266daef5901bab8f505d3dec07d402a7d39933" + name = "github.com/btcsuite/btcutil" + packages = [ + ".", + "base58", + "bech32", + "gcs", + "gcs/builder", + "hdkeychain", + ] + pruneopts = "UT" + revision = "02a4fd9de1d52e877491996349a92d54c653ccbf" + version = "v1.0.1" + +[[projects]] + digest = "1:8afcc19670e29d6b7c3cb99114d12af4bfab2dd0ceb3a24934ceabe1ff3596ac" + name = "github.com/btcsuite/btcwallet" + packages = [ + "chain", + "internal/legacy/keystore", + "internal/legacy/rename", + "internal/prompt", + "internal/zero", + "snacl", + "waddrmgr", + "wallet", + "wallet/txauthor", + "wallet/txrules", + "wallet/txsizes", + "walletdb", + "walletdb/migration", + "wtxmgr", + ] + pruneopts = "UT" + revision = "b19df70dddb66b27902f48cc48e69741909ef2e9" + version = "v0.11.0" + +[[projects]] + branch = "master" + digest = "1:1e6b2f7aa98b082c30a1303c29a702c369b2ec6d86b74a599bc8bbe2333db299" + name = "github.com/btcsuite/go-socks" + packages = ["socks"] + pruneopts = "UT" + revision = "4720035b7bfd2a9bb130b1c184f8bbe41b6f0d0f" + +[[projects]] + branch = "master" + digest = "1:0b2242fd2f4f51fb491e97e204464f75da680897a0db10ec7554c87b71b5afc3" + name = "github.com/btcsuite/websocket" + packages = ["."] + pruneopts = "UT" + revision = "31079b6807923eb23992c421b114992b95131b55" + +[[projects]] + digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" name = "github.com/codegangsta/cli" packages = ["."] + pruneopts = "UT" revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" version = "v1.20.0" [[projects]] branch = "master" + digest = "1:b260a550e12c059465212cfe9f1b0ace832aea6235d5e076e6fae0e66c882ef5" name = "github.com/codelingo/antlr" packages = ["runtime/Go/antlr"] + pruneopts = "UT" revision = "afe378d7da74af180f7813c50f033de669e0e477" [[projects]] branch = "master" + digest = "1:d2a76bb2bcd9790187245c57e85edc1815af9bce23393fa3393beeda5036782f" name = "github.com/codelingo/clql" packages = [ "dotlingo", - "inner" + "inner", ] - revision = "45d204b5076b28931a0a0c3556fc22e3d7c681d4" + pruneopts = "UT" + revision = "4a63b1003f8bf59c1c585fc2cca58f6083926aac" [[projects]] branch = "master" + digest = "1:1a5ac00623e34f7f6a2293aaf55ff3f105760c513fe3f6cceef2e83e079a117b" name = "github.com/codelingo/lingo" packages = [ "app/commands/verify", @@ -62,33 +179,57 @@ "service/grpc", "vcs", "vcs/git", - "vcs/p4" + "vcs/p4", ] - revision = "fc18bb8b8a83c483aa58606f0695de1e1cd55f1b" + pruneopts = "UT" + revision = "299656113649d54923cc9b6c0872248c115450dd" [[projects]] branch = "master" + digest = "1:b5ba3f29d3763f6962adefe157a136cbb2d67e19950bdb60035490b776ddbe6e" name = "github.com/codelingo/rpc" packages = [ "flow", "flow/client", - "service" + "service", ] - revision = "7b2791905445e15a2ddb59ee823eb8c6d44a52a9" + pruneopts = "UT" + revision = "d205101a7bc2f6f843a60f3d8de0f7e19cae2617" [[projects]] branch = "master" + digest = "1:7bbfeef88a6cc7d65a6a009ba696e980dcf84ed31a7dd6da7e0c6c2ff544df92" name = "github.com/common-nighthawk/go-figure" packages = ["."] + pruneopts = "UT" revision = "18b2b544842cab16f3ff418fb5290d8f2896a069" [[projects]] branch = "master" + digest = "1:fc8dbcc2a5de7c093e167828ebbdf551641761d2ad75431d3a167d467a264115" name = "github.com/containerd/continuity" packages = ["pathdriver"] + pruneopts = "UT" revision = "0377f7d767206f3a9e8881d0f02267b0d89c7a62" [[projects]] + digest = "1:c7db0d7eb0409eda9ac91b3a9d9ed49a019d0c9c82c6dd4e4bfcf97267c7d435" + name = "github.com/coreos/bbolt" + packages = ["."] + pruneopts = "UT" + revision = "68cc10a767ea1c6b9e8dcb9847317ff192d6d974" + version = "v1.3.4" + +[[projects]] + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:d665be6d68a70466a6df6939d9d7dace71df3d7ecbea190e7abb9b48f8d4c37c" name = "github.com/docker/docker" packages = [ "api/types", @@ -115,94 +256,136 @@ "pkg/stdcopy", "pkg/system", "pkg/term", - "pkg/term/windows" + "pkg/term/windows", ] + pruneopts = "UT" revision = "fe8aac6f5ae413a967adb0adad0b54abdfb825c4" [[projects]] + digest = "1:ade935c55cd6d0367c843b109b09c9d748b1982952031414740750fdf94747eb" name = "github.com/docker/go-connections" packages = ["nat"] + pruneopts = "UT" revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55" version = "v0.4.0" [[projects]] + digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18" name = "github.com/docker/go-units" packages = ["."] + pruneopts = "UT" revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" version = "v0.3.3" [[projects]] + digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a" name = "github.com/fatih/color" packages = ["."] + pruneopts = "UT" revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" version = "v1.7.0" [[projects]] + digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" name = "github.com/fsnotify/fsnotify" packages = ["."] + pruneopts = "UT" revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" version = "v1.4.7" [[projects]] + digest = "1:430effbc90294ae7bee0576a2e1c311decf5d88d4ad3959b756906d5ada9f266" name = "github.com/fsouza/go-dockerclient" packages = ["."] + pruneopts = "UT" revision = "2ff310040c161b75fa19fb9b287a90a6e03c0012" version = "v1.1.0" [[projects]] branch = "master" + digest = "1:36fe9527deed01d2a317617e59304eb2c4ce9f8a24115bcc5c2e37b3aee5bae4" name = "github.com/gin-contrib/sse" packages = ["."] + pruneopts = "UT" revision = "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae" [[projects]] + digest = "1:489e108f21464371ebf9cb5c30b1eceb07c6dd772dff073919267493dd9d04ea" name = "github.com/gin-gonic/gin" packages = [ ".", "binding", - "render" + "render", ] + pruneopts = "UT" revision = "d459835d2b077e44f7c9b453505ee29881d5d12d" version = "v1.2" +[[projects]] + digest = "1:aacef5f5e45685f2aeda5534d0a750dee6859de7e9088cdd06192787bb01ae6d" + name = "github.com/go-errors/errors" + packages = ["."] + pruneopts = "UT" + revision = "a6af135bd4e28680facf08a3d206b454abc877a4" + version = "v1.0.1" + +[[projects]] + digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + name = "github.com/go-yaml/yaml" + packages = ["."] + pruneopts = "UT" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + [[projects]] branch = "master" + digest = "1:45c26d250d23d0339258cffbcaf713eb0160896c5f1a99717662e122ec1b4377" name = "github.com/gogits/go-gogs-client" packages = ["."] + pruneopts = "UT" revision = "c84e492d93458e0bb79bfa1810047841d5bb12ae" [[projects]] + digest = "1:bbadccf3d3317ea03c0dac0b45b673b4b397c8f91a1d2eff550a3c51c4ad770e" name = "github.com/gogo/protobuf" packages = ["proto"] + pruneopts = "UT" revision = "636bf0302bc95575d69441b25a2603156ffdddf1" version = "v1.1.1" [[projects]] + digest = "1:239c4c7fd2159585454003d9be7207167970194216193a8a210b8d29576f19c9" name = "github.com/golang/protobuf" packages = [ "proto", "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp" + "ptypes/timestamp", ] - revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" - version = "v1.2.0" + pruneopts = "UT" + revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30" + version = "v1.3.1" [[projects]] + digest = "1:f4f203acd8b11b8747bdcd91696a01dbc95ccb9e2ca2db6abf81c3a4f5e950ce" name = "github.com/google/go-github" packages = ["github"] + pruneopts = "UT" revision = "f55b50f38167644bb7e4be03d9a2bde71d435239" version = "v18.2.0" [[projects]] + digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690" name = "github.com/google/go-querystring" packages = ["query"] + pruneopts = "UT" revision = "44c6ddd0a2342c386950e880b658017258da92fc" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:a361611b8c8c75a1091f00027767f7779b29cb37c456a71b8f2604c88057ab40" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -214,251 +397,425 @@ "hcl/token", "json/parser", "json/scanner", - "json/token" + "json/token", ] + pruneopts = "UT" revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" [[projects]] branch = "master" + digest = "1:c332ef8a8f0eec41f75f8f39c885f650dab0c3de426bde6b37fff6a823feab25" name = "github.com/hishboy/gocommons" packages = ["lang"] + pruneopts = "UT" revision = "89887b2ade6d89e3b322e2e3c8e337ad5c6a447b" [[projects]] branch = "master" + digest = "1:f3b0cfd42716269884bf06df5d5be9e54161d5af0a2f0ccca08717928dd45fc5" name = "github.com/inconshreveable/go-update" packages = [ ".", "internal/binarydist", - "internal/osext" + "internal/osext", ] + pruneopts = "UT" revision = "8152e7eb6ccf8679a64582a66b78519688d156ad" [[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] + pruneopts = "UT" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" +[[projects]] + digest = "1:4b6a85c651ea3faa6dfb4e6de5249b602226270b9eb3dfb8f49ec91fa3ef08ff" + name = "github.com/jrick/logrotate" + packages = ["rotator"] + pruneopts = "UT" + revision = "a93b200c26cbae3bb09dd0dc2c7c7fe1468a034a" + version = "v1.0.0" + [[projects]] branch = "master" + digest = "1:ba8f64ba7ccf5eb99d78d3e519045b42c2c8d6ea55938176b54e918e791b3b0d" name = "github.com/juju/clock" packages = ["."] + pruneopts = "UT" revision = "bab88fc672997ef02d03f85310182d97a93dee21" [[projects]] branch = "master" + digest = "1:53bd4347b151fcbedcdda527f7ebf4924f0e21d672131812f857175d8c7a1051" name = "github.com/juju/errors" packages = ["."] + pruneopts = "UT" revision = "812b06ada1776ad4dd95d575e18ffffe3a9ac34a" [[projects]] branch = "master" + digest = "1:a330103bc9731260ee9fa14764e9e3fce46e02de19d6aca3eeba1d425badfbf0" name = "github.com/juju/loggo" packages = ["."] + pruneopts = "UT" revision = "584905176618da46b895b176c721b02c476b6993" [[projects]] branch = "master" + digest = "1:cc5c45b8f92cdabb759e1f471a1bd7ae51da36ea74c1a0d1d71b9e195d85162f" name = "github.com/juju/testing" packages = ["checkers"] + pruneopts = "UT" revision = "472a3e8b2073fb3cd67c12d2bc5f3cd28e5f4116" [[projects]] + digest = "1:306e69960dffb60f242cc35256a053ca9712ca801fdbb4e49879d76a4415a33e" + name = "github.com/kkdai/bstream" + packages = ["."] + pruneopts = "UT" + revision = "a8d27c5aa8eedad6b6683069d64b96ece63696db" + version = "v1.0.0" + +[[projects]] + digest = "1:ca955a9cd5b50b0f43d2cc3aeb35c951473eeca41b34eb67507f1dbcc0542394" name = "github.com/kr/pretty" packages = ["."] + pruneopts = "UT" revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712" version = "v0.1.0" [[projects]] + digest = "1:15b5cc79aad436d47019f814fde81a10221c740dc8ddf769221a65097fb6c2e9" name = "github.com/kr/text" packages = ["."] + pruneopts = "UT" revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f" version = "v0.1.0" [[projects]] + branch = "master" + digest = "1:0174e61ac7925014230ea874358ecd765dc253d0e0f079c8e902b38de8f50e76" + name = "github.com/lightninglabs/gozmq" + packages = ["."] + pruneopts = "UT" + revision = "d20a764486bf506bc045642e455bc7f0d21b232a" + +[[projects]] + digest = "1:1408bf2486c0b8cede54b0fd0112456daff1db45d78cc9a00a3c83b5988124a1" + name = "github.com/lightninglabs/neutrino" + packages = [ + ".", + "banman", + "blockntfns", + "cache", + "cache/lru", + "chainsync", + "filterdb", + "headerfs", + "headerlist", + "pushtx", + "query", + ] + pruneopts = "UT" + revision = "e6008cebf1d97fc49e468d8caa8b6eb13f27cbe1" + version = "v0.11.0" + +[[projects]] + digest = "1:89ed66906e6bf0f053c58dfcba58a034d2fe0126c749dcb31c093faed9aa292b" + name = "github.com/lightningnetwork/lightning-onion" + packages = ["."] + pruneopts = "UT" + revision = "25954be99ab05ad18ae745a00f81f271ae3590c4" + version = "v1.0.1" + +[[projects]] + digest = "1:d1acda46fb621d98651fc6ab933f56d0e74bf9ddd42613f8a5226d4f5887dad1" + name = "github.com/lightningnetwork/lnd" + packages = [ + "build", + "channeldb", + "channeldb/migration12", + "channeldb/migration_01_to_11", + "htlcswitch/hop", + "input", + "keychain", + "lntypes", + "lnwire", + "queue", + "record", + "routing/route", + "shachain", + "ticker", + "tlv", + "tor", + "zpay32", + ] + pruneopts = "UT" + revision = "ab87dc5fe2fc5f9b3f8c4d5d6cabea796f46ae2d" + version = "v0.9.2-beta" + +[[projects]] + digest = "1:1eed3814667111a72d3c342fdb99fe6ea8f280334e140f153ec32d1fff4f1f80" + name = "github.com/ltcsuite/ltcd" + packages = [ + "chaincfg", + "chaincfg/chainhash", + "wire", + ] + pruneopts = "UT" + revision = "4faa85afe0ded0dc7316ad0fa2912c3ae3507794" + version = "v0.20.1-beta" + +[[projects]] + digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7" name = "github.com/magiconair/properties" packages = ["."] + pruneopts = "UT" revision = "c2353362d570a7bfa228149c62842019201cfb71" version = "v1.8.0" [[projects]] branch = "master" + digest = "1:52c7f1340963185f8368882d3b1eae1e9e97de2958ad97136c2a9333633dae57" name = "github.com/marcinwyszynski/directory_tree" packages = ["."] + pruneopts = "UT" revision = "a96c3845f18c5e77ac6780080701e52adc86817c" [[projects]] + digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" name = "github.com/mattn/go-colorable" packages = ["."] + pruneopts = "UT" revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" version = "v0.0.9" [[projects]] + digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb" name = "github.com/mattn/go-isatty" packages = ["."] + pruneopts = "UT" revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" +[[projects]] + digest = "1:3e2a07391c0756e06504dfa2fb6c52d55261c8d5a9d790ab316a1544bbe87b0d" + name = "github.com/miekg/dns" + packages = ["."] + pruneopts = "UT" + revision = "f515aa579d28efa1af67d9a62cc57f2dfe59da76" + version = "v1.1.29" + [[projects]] branch = "master" + digest = "1:8eb17c2ec4df79193ae65b621cd1c0c4697db3bc317fe6afdc76d7f2746abd05" name = "github.com/mitchellh/go-homedir" packages = ["."] + pruneopts = "UT" revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66" [[projects]] branch = "master" + digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355" name = "github.com/mitchellh/mapstructure" packages = ["."] + pruneopts = "UT" revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac" [[projects]] + digest = "1:ee4d4af67d93cc7644157882329023ce9a7bcfce956a079069a9405521c7cc8d" name = "github.com/opencontainers/go-digest" packages = ["."] + pruneopts = "UT" revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf" version = "v1.0.0-rc1" [[projects]] + digest = "1:11db38d694c130c800d0aefb502fb02519e514dc53d9804ce51d1ad25ec27db6" name = "github.com/opencontainers/image-spec" packages = [ "specs-go", - "specs-go/v1" + "specs-go/v1", ] + pruneopts = "UT" revision = "d60099175f88c47cd379c4738d158884749ed235" version = "v1.0.1" [[projects]] + digest = "1:1869683e323ebff2bdf8adcb560f82bf6f8d94019d35099e3403f7df12e9c07e" name = "github.com/opencontainers/runc" packages = [ "libcontainer/system", - "libcontainer/user" + "libcontainer/user", ] + pruneopts = "UT" revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" version = "v0.1.1" [[projects]] + digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" name = "github.com/pelletier/go-toml" packages = ["."] + pruneopts = "UT" revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" version = "v1.2.0" [[projects]] + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "UT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" name = "github.com/pmezard/go-difflib" packages = ["difflib"] + pruneopts = "UT" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:16e6fde687529deaefbeff14841b694cb6d67dfc31884bac6214313e96e107bc" name = "github.com/rhysd/go-github-selfupdate" packages = ["selfupdate"] + pruneopts = "UT" revision = "6329a43531c520b7044ee4a595f09f0e3ed9f7ce" [[projects]] + digest = "1:b36a0ede02c4c2aef7df7f91cbbb7bb88a98b5d253509d4f997dda526e50c88c" name = "github.com/russross/blackfriday" packages = ["."] + pruneopts = "UT" revision = "05f3235734ad95d0016f6a23902f06461fcf567a" version = "v1.5.2" [[projects]] branch = "master" + digest = "1:8baa3b16f20963c54e296627ea1dabfd79d1b486f81baf8759e99d73bddf2687" name = "github.com/samfoo/ansi" packages = ["."] + pruneopts = "UT" revision = "b6bd2ded7189ce35bc02233b554eb56a5146af73" [[projects]] + digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" name = "github.com/sirupsen/logrus" packages = ["."] + pruneopts = "UT" revision = "3e01752db0189b9157070a0e1668a620f9a85da2" version = "v1.0.6" [[projects]] + digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84" name = "github.com/spf13/afero" packages = [ ".", - "mem" + "mem", ] + pruneopts = "UT" revision = "787d034dfe70e44075ccc060d346146ef53270ad" version = "v1.1.1" [[projects]] + digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" name = "github.com/spf13/cast" packages = ["."] + pruneopts = "UT" revision = "8965335b8c7107321228e3e3702cab9832751bac" version = "v1.2.0" [[projects]] + digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939" name = "github.com/spf13/cobra" packages = ["."] + pruneopts = "UT" revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" version = "v0.0.3" [[projects]] branch = "master" + digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805" name = "github.com/spf13/jwalterweatherman" packages = ["."] + pruneopts = "UT" revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" [[projects]] + digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7" name = "github.com/spf13/pflag" packages = ["."] + pruneopts = "UT" revision = "583c0c0531f06d5278b7d917446061adc344b5cd" version = "v1.0.1" [[projects]] + digest = "1:59e7dceb53b4a1e57eb1eb0bf9951ff0c25912df7660004a789b62b4e8cdca3b" name = "github.com/spf13/viper" packages = ["."] + pruneopts = "UT" revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736" version = "v1.0.2" [[projects]] + digest = "1:ebabd8967efa341b15e51b329eb5b7ebc69b573fb4adb4ac2433fa532edcadbc" name = "github.com/tcnksm/go-gitconfig" packages = ["."] + pruneopts = "UT" revision = "d154598bacbf4501c095a309753c5d4af66caa81" version = "v0.1.2" [[projects]] + digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3" name = "github.com/ugorji/go" packages = ["codec"] + pruneopts = "UT" revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab" version = "v1.1.1" [[projects]] + digest = "1:4aeb3860275fa1fd60cccfb5a6ef85da438bf17402e1e84412ade4d4b55066a0" name = "github.com/ulikunitz/xz" packages = [ ".", "internal/hash", "internal/xlog", - "lzma" + "lzma", ] + pruneopts = "UT" revision = "0c6b41e72360850ca4f98dc341fd999726ea007f" version = "v0.5.4" [[projects]] + digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" name = "github.com/urfave/cli" packages = ["."] + pruneopts = "UT" revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" version = "v1.20.0" [[projects]] + digest = "1:3c1a69cdae3501bf75e76d0d86dc6f2b0a7421bc205c0cb7b96b19eed464a34d" name = "go.uber.org/atomic" packages = ["."] + pruneopts = "UT" revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289" version = "v1.3.2" [[projects]] + digest = "1:60bf2a5e347af463c42ed31a493d817f8a72f102543060ed992754e689805d1a" name = "go.uber.org/multierr" packages = ["."] + pruneopts = "UT" revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" version = "v1.1.0" [[projects]] + digest = "1:53abd1494b5c246021b5b8dc3a13001c8ca1172eb88517c51b96d0f51ec81dcf" name = "go.uber.org/zap" packages = [ ".", @@ -466,51 +823,80 @@ "internal/bufferpool", "internal/color", "internal/exit", - "zapcore" + "zapcore", ] + pruneopts = "UT" revision = "9d9d6135afe89b6fc4a05e9a8552526caba38048" version = "v1.5.0" [[projects]] branch = "master" + digest = "1:52d4ec64c14c33c677a866ca7b70019358f71e905d21eded91047875bc821736" name = "golang.org/x/crypto" - packages = ["ssh/terminal"] + packages = [ + "ed25519", + "ed25519/internal/edwards25519", + "internal/subtle", + "nacl/secretbox", + "pbkdf2", + "poly1305", + "ripemd160", + "salsa20/salsa", + "scrypt", + "ssh/terminal", + ] + pruneopts = "UT" revision = "c126467f60eb25f8f27e5a981f32a87e3965053f" [[projects]] branch = "master" + digest = "1:987dd4556a5577c868f6eeeab6d3d2d7a28553485e51fc3149bad6fb14652ce0" name = "golang.org/x/net" packages = [ + "bpf", "context", "context/ctxhttp", "http/httpguts", "http2", "http2/hpack", "idna", + "internal/iana", + "internal/socket", + "internal/socks", "internal/timeseries", - "trace" + "ipv4", + "ipv6", + "proxy", + "trace", ] + pruneopts = "UT" revision = "c4299a1a0d8524c11563db160fbf9bddbceadb21" [[projects]] branch = "master" + digest = "1:28b360454e161aae382c007449bcb1296087b5b52b4f37623bba0f9f9d7207f4" name = "golang.org/x/oauth2" packages = [ ".", - "internal" + "internal", ] + pruneopts = "UT" revision = "c57b0facaced709681d9f90397429b9430a74754" [[projects]] branch = "master" + digest = "1:a220a85c72a6cb7339c412cb2b117019a7fd94007cdfffb3b5b1d058227a2bf8" name = "golang.org/x/sys" packages = [ + "cpu", "unix", - "windows" + "windows", ] + pruneopts = "UT" revision = "bd9dbc187b6e1dacfdd2722a87e83093c2d7bd6e" [[projects]] + digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" name = "golang.org/x/text" packages = [ "collate", @@ -526,12 +912,31 @@ "unicode/bidi", "unicode/cldr", "unicode/norm", - "unicode/rangetable" + "unicode/rangetable", ] + pruneopts = "UT" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] + branch = "master" + digest = "1:eeeca39588235f555a835e13d67b4f13477f5a231a80ca111e886fa3864af4d1" + name = "golang.org/x/tools" + packages = [ + "go/ast/astutil", + "go/gcexportdata", + "go/internal/gcimporter", + "go/internal/packagesdriver", + "go/packages", + "go/types/typeutil", + "internal/gocommand", + "internal/packagesinternal", + ] + pruneopts = "UT" + revision = "fa3cc9eebcfea6574930b14f65917ce74e6fa4fd" + +[[projects]] + digest = "1:6247f76e55a1e1a5c19a81e2d4b4dff6730461eeb5bbb0a16dd4a8ec8637ee93" name = "google.golang.org/appengine" packages = [ "internal", @@ -540,35 +945,44 @@ "internal/log", "internal/remote_api", "internal/urlfetch", - "urlfetch" + "urlfetch", ] + pruneopts = "UT" revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06" version = "v1.2.0" [[projects]] branch = "master" + digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] + pruneopts = "UT" revision = "2a72893556e4d1f6c795a4c039314c9fa751eedb" [[projects]] + digest = "1:c00eb80d7b152379c3e94c38d82b29deca98b1d0f53e4e20362589b7fcbffa07" name = "google.golang.org/grpc" packages = [ ".", "balancer", "balancer/base", "balancer/roundrobin", + "binarylog/grpc_binarylog_v1", "codes", "connectivity", "credentials", + "credentials/internal", "encoding", "encoding/proto", "grpclog", "internal", "internal/backoff", + "internal/binarylog", "internal/channelz", "internal/envconfig", "internal/grpcrand", + "internal/grpcsync", + "internal/syscall", "internal/transport", "keepalive", "metadata", @@ -579,71 +993,124 @@ "resolver/passthrough", "stats", "status", - "tap" + "tap", ] - revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1" - version = "v1.15.0" + pruneopts = "UT" + revision = "3507fb8e1a5ad030303c106fef3a47c9fdad16ad" + version = "v1.19.1" [[projects]] branch = "v1" + digest = "1:af715ae33cc1f5695c4b2a4e4b21d008add8802a99e15bb467ac7c32edb5000d" name = "gopkg.in/check.v1" packages = ["."] + pruneopts = "UT" revision = "788fd78401277ebd861206a03c884797c6ec5541" [[projects]] + digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a" name = "gopkg.in/fatih/color.v1" packages = ["."] + pruneopts = "UT" revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" version = "v1.7.0" [[projects]] + digest = "1:cbc72c4c4886a918d6ab4b95e347ffe259846260f99ebdd8a198c2331cf2b2e9" name = "gopkg.in/go-playground/validator.v8" packages = ["."] + pruneopts = "UT" revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" version = "v8.18.2" [[projects]] branch = "v1" + digest = "1:ed175b7c1c9fbb1a3a0f9985096fecf0d3e5234a05a3cf870a84680d7714fae5" name = "gopkg.in/juju/worker.v1" packages = ["."] + pruneopts = "UT" revision = "e63ca51523be524b34f05ffa0c62eed10e231f8f" [[projects]] branch = "v2" + digest = "1:2642fd0b6900c77247d61d80cf2eb59a374ef4ffc2d25a1b95b87dc355b2894e" name = "gopkg.in/mgo.v2" packages = [ "bson", - "internal/json" + "internal/json", ] + pruneopts = "UT" revision = "9856a29383ce1c59f308dd1cf0363a79b5bef6b5" [[projects]] branch = "v1" + digest = "1:0caa92e17bc0b65a98c63e5bc76a9e844cd5e56493f8fdbb28fad101a16254d9" name = "gopkg.in/tomb.v1" packages = ["."] + pruneopts = "UT" revision = "dd632973f1e7218eb1089048e0798ec9ae7dceb8" [[projects]] branch = "v2" + digest = "1:5bb148b78468350091db2ffbb2370f35cc6dcd74d9378a31b1c7b86ff7528f08" name = "gopkg.in/tomb.v2" packages = ["."] + pruneopts = "UT" revision = "d5d1b5820637886def9eef33e03a27a9f166942c" [[projects]] branch = "v1" + digest = "1:4384086c0f7f9e662c2ae8e900af35dc75ff262205b362597bb65af2fd8a21c9" name = "gopkg.in/yaml.v1" packages = ["."] + pruneopts = "UT" revision = "9f9df34309c04878acc86042b16630b0f696e1de" [[projects]] + digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "UT" revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" version = "v2.2.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "efb0b0c490b16341dde81fe8869a34eaef8c2243cd1057a246eb195837721177" + input-imports = [ + "github.com/codelingo/clql/dotlingo", + "github.com/codelingo/lingo/app/commands/verify", + "github.com/codelingo/lingo/app/util", + "github.com/codelingo/lingo/app/util/common/config", + "github.com/codelingo/lingo/service", + "github.com/codelingo/lingo/service/grpc", + "github.com/codelingo/lingo/vcs", + "github.com/codelingo/rpc/flow", + "github.com/codelingo/rpc/flow/client", + "github.com/common-nighthawk/go-figure", + "github.com/fatih/color", + "github.com/gin-gonic/gin", + "github.com/golang/protobuf/proto", + "github.com/golang/protobuf/ptypes", + "github.com/juju/errors", + "github.com/juju/testing/checkers", + "github.com/lightningnetwork/lnd/channeldb", + "github.com/marcinwyszynski/directory_tree", + "github.com/mitchellh/go-homedir", + "github.com/pkg/errors", + "github.com/russross/blackfriday", + "github.com/samfoo/ansi", + "github.com/spf13/cobra", + "github.com/spf13/pflag", + "github.com/spf13/viper", + "github.com/urfave/cli", + "golang.org/x/net/context", + "google.golang.org/grpc", + "gopkg.in/check.v1", + "gopkg.in/juju/worker.v1", + "gopkg.in/tomb.v1", + "gopkg.in/yaml.v1", + "gopkg.in/yaml.v2", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index 90564814..16b754bc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -

codelingo

+

+ +

- Automate Your Reviews on GitHub Pull Requests! + Drive Continuous Higher Standards with CodeLingo's Automated Reviews, Bug Fixes and Docs!

@@ -14,55 +16,68 @@ ## Overview -Codelingo is a Platform as a Service (PaaS) that helps software development teams produce better software, faster - together. It treats **your software as data** and **automates your workflows**, called Flows, with the rules and patterns you define, called Tenets. +CodeLingo finds and fixes issues in existing code and ensures the same issues don't sneak in with new pull requests. CodeLingo also generates your contributor docs thus automating three cornerstones (bug fixing, reviewing and doc updates) to scale up your code quality. -Our flagship Flow is the Review Flow, which checks a repository's pull requests conform to its project specific patterns. -## Vision & Mission + + +## @CodeLingoBot Quick Start -Our vision is for Codelingo to be the protocol on top of which the insights and experience of developers across the industry can be shared and applied. We are on a mission to build the CodeLingo community: starting with teams solving their own problems, we'll build out the rich repository of Tenets and Flows together. +@CodeLingoBot fixes bugs, automates code reviews and updates contributor docs for repos on GitHub based on rules, called Rules, defined in codelingo.yaml files. There are several ways you can interact with @CodeLingoBot. The simplest is to trigger actions by commenting on a Pull Request: -## Quick Starts ++ `@CodeLingoBot review` reviews the PR based on default Rules for the repo's language or custom Rules added in a codelingo.yaml file in the root of the repo.

You can trigger CodeLingo actions by replying to comments generated by`@review` + + `@CodeLingoBot review ignore [reason]` ignores the issue for current and future reviews with an optional reason why. + + `@CodeLingoBot review un-ignore` un-ignores the issue for current and future reviews. ++ `@CodeLingoBot rewrite` makes a PR to this PR fixing the issues found based on default Rules for the repo's language or custom Rules added in a codelingo.yaml file in the root of the repo.
You can trigger CodeLingo actions by replying to comments generated by`@rewrite` + + + `@CodeLingoBot rewrite set ` sets the value of a variable in a rewrite template. -### Playground +To have @CodeLingoBot automatically review every new Pull Request to your repo, [install CodeLingo on your repo](https://github.com/apps/codelingo). If you don't already have a codelingo.yaml file, @CodeLingoBot will make a PR to add one to your repo. This will contain a bundle of Rules based on the languages in your repo. -Test out writing and running a Tenet online with zero installs on the [playground](https://codelingo.io/playground) - it's easier than you think! +Every pull request to your repository will now be checked against the go Rule bundle we imported above:

- - - +

- - -### GitHub Review Flow +### Editing codelingo.yaml -After installing [Codelingo on GitHub](https://github.com/apps/codelingo), write the following codelingo.yaml to the root of your repository: +Let @CodeLingoBot know what Rules you'd like it to enforce by adding or +removing them from codelingo.yaml. You can find Rules to add using the [CodeLingo +Dashboard](https://dash.codelingo.io). For example, if we wanted to install the "go" bundle written by "codelingo" we'd update the codelingo.yaml as follows: ```yaml # codelingo.yaml file -tenets: +rules: - import: codelingo/go ``` -Every pull request to your repository will now be checked against the go Tenet bundle we imported above. +## Power Users! -

- -

+The rest of this README is for users interested in writing their own Rules and testing out CodeLingo on repositories on their local machine. + +### Contributing a Rule or Rule Bundle + +The Rule bundles that are rendered on codelingo.io/Rules are populated by the Rules in the [Rules directory](https://github.com/codelingo/codelingo/tree/master/tenets) in this repository. Contributing a new Rule is simply a matter of making a PR to this repo. We're a young community of Rule authors eager to help and guide you through your first PR. +Get started with the [docs](https://www.codelingo.io/docs) and find us at [codelingo.slack.com](https://codelingo.slack.com)! -Other Tenet bundles (including for other languages) from the community can be found under the [tenets directory](https://github.com/codelingo/codelingo/tree/master/tenets) in this repository. +### Writing a Rule - +#### Playground +Each Rule has a `query`, written in [CLQL](https://www.codelingo.io/docs/concepts/CLQL/), to match a pattern. The online [playground](https://codelingo.io/playground) is a quick way to test and learn CLQL. It has a box of source code next to a box of CLQL. Select the source code and the playground will automatically generate the CLQL to match that selection! +

+ + + +

-### Local Review Flow +#### Setting up CodeLingo on your Dev Box -To run the Review Flow against repositories on your local machine, install the [lingo CLI](https://github.com/codelingo/lingo/releases/latest) and set it up with the following commands: +Before we get into writing Rules, we want to ensure we can run and test them locally. For this, we're going to use the lingo CLI tool to run a review on our local machine. First, install the [lingo CLI](https://github.com/codelingo/lingo/releases/latest) and set it up with the following commands: ```bash # Run this command from anywhere. Follow the prompts to set up Codelingo on your machine. @@ -75,11 +90,11 @@ $ lingo init Replace the content of the codelingo.yaml file we wrote above with: ```yaml - tenets: + rules: - import: codelingo/go ``` -You can now run the Review Flow to check your source code against the go Tenet bundle we imported above. +You can now run the review to check your source code against the go Rule bundle we imported above. ```bash # Run this command from the same directory as the codelingo.yaml file or any of its sub directories. @@ -90,10 +105,11 @@ $ lingo run review

- ## Next Steps -See the [getting started guide](https://www.codelingo.io/docs/#getting-started) to learn more about Tenets, Flows and the Codelingo Query Language (CLQL). +Visit your [CodeLingo Dashboard](https://dash.codelingo.io) + +You now have the tools setup to start writing your own Rules! See the [getting started guide](https://www.codelingo.io/docs/#getting-started) to learn more about Rules and how to write them. ## Resources @@ -102,13 +118,12 @@ See the [getting started guide](https://www.codelingo.io/docs/#getting-started) - [slack](https://join.slack.com/t/codelingo/shared_invite/enQtNDYxOTYyNTI5NjUwLWFiNjFjOTM3YzgzMjA4NjNiNDhmN2RkZWNlODM0ZTM5NTkzOThhZjczN2ZlYmNkMjhkNDBkYjBlMjQ1NDk2NTQ) - - [codelingo.io/discuss](http://codelingo.io/discuss) - [hello@codelingo.io](mailto:hello@codelingo.io) ### Learn -- [codelingo.io/playground](https://codelingo.io/playground) - Write, automatically generate and run Tenets and Flows online. -- [codelingo.io/docs](https://codelingo.io/docs) - Learn to write (and automatically generate!) Tenets and compose Flows. +- [codelingo.io/playground](https://codelingo.io/playground) - Write, automatically generate and run Rules and Flows online. +- [codelingo.io/docs](https://codelingo.io/docs) - Learn to write (and automatically generate!) Rules and compose Flows. ### Repos diff --git a/flows/README.md b/flows/README.md index 81f8f0d1..3f71d5ff 100644 --- a/flows/README.md +++ b/flows/README.md @@ -1,3 +1,10 @@ # Flows Flow configurations that can be deployed on the CodeLingo Platform. + +## Build flow CLI from source + +```bash +cd / +go build -o ~/.codelingo/flows///cmd +``` \ No newline at end of file diff --git a/flows/codelingo/_functions/bin/darwin/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/_functions/bin/darwin/amd64/0.0.0/cmd.tar.gz index cae20d57..25fb8a65 100644 Binary files a/flows/codelingo/_functions/bin/darwin/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/_functions/bin/darwin/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/_functions/bin/linux/386/0.0.0/cmd.tar.gz b/flows/codelingo/_functions/bin/linux/386/0.0.0/cmd.tar.gz deleted file mode 100644 index cae20d57..00000000 Binary files a/flows/codelingo/_functions/bin/linux/386/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/_functions/bin/linux/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/_functions/bin/linux/amd64/0.0.0/cmd.tar.gz index cae20d57..25fb8a65 100644 Binary files a/flows/codelingo/_functions/bin/linux/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/_functions/bin/linux/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/docs/bin/darwin/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/docs/bin/darwin/amd64/0.0.0/cmd.tar.gz index 0dba331b..a8bbf21f 100644 Binary files a/flows/codelingo/docs/bin/darwin/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/docs/bin/darwin/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/docs/bin/linux/386/0.0.0/cmd.tar.gz b/flows/codelingo/docs/bin/linux/386/0.0.0/cmd.tar.gz deleted file mode 100644 index 9e07ad43..00000000 Binary files a/flows/codelingo/docs/bin/linux/386/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/docs/bin/linux/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/docs/bin/linux/amd64/0.0.0/cmd.tar.gz index cd809b95..6d1e534e 100644 Binary files a/flows/codelingo/docs/bin/linux/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/docs/bin/linux/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/docs/bin/windows/386/0.0.0/cmd.exe.zip b/flows/codelingo/docs/bin/windows/386/0.0.0/cmd.exe.zip deleted file mode 100644 index 60eb5efd..00000000 Binary files a/flows/codelingo/docs/bin/windows/386/0.0.0/cmd.exe.zip and /dev/null differ diff --git a/flows/codelingo/docs/bin/windows/amd64/0.0.0/cmd.exe.zip b/flows/codelingo/docs/bin/windows/amd64/0.0.0/cmd.exe.zip index ba4f495a..1ef30d71 100644 Binary files a/flows/codelingo/docs/bin/windows/amd64/0.0.0/cmd.exe.zip and b/flows/codelingo/docs/bin/windows/amd64/0.0.0/cmd.exe.zip differ diff --git a/flows/codelingo/docs/main.go b/flows/codelingo/docs/main.go index 5929e630..cd3229ad 100644 --- a/flows/codelingo/docs/main.go +++ b/flows/codelingo/docs/main.go @@ -32,7 +32,16 @@ var docsApp = &flowutil.CLIApp{ cli.StringFlag{ Name: "template, t", Value: "default", - Usage: "The template file to use when generating docs.", + Usage: ` +The template file to use when generating docs. Default template: + + # Contributor Guide + {{range .}} + ## {{.title}} + {{.body}} + {{end}} + +`[1:], }, cli.StringFlag{ Name: "format, f", diff --git a/flows/codelingo/pull-request/bin/darwin/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/pull-request/bin/darwin/amd64/0.0.0/cmd.tar.gz deleted file mode 100644 index 8eafa949..00000000 Binary files a/flows/codelingo/pull-request/bin/darwin/amd64/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/pull-request/bin/linux/386/0.0.0/cmd.tar.gz b/flows/codelingo/pull-request/bin/linux/386/0.0.0/cmd.tar.gz deleted file mode 100644 index cae20d57..00000000 Binary files a/flows/codelingo/pull-request/bin/linux/386/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/pull-request/bin/linux/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/pull-request/bin/linux/amd64/0.0.0/cmd.tar.gz deleted file mode 100644 index 8eafa949..00000000 Binary files a/flows/codelingo/pull-request/bin/linux/amd64/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/pull-request/bin/windows/amd64/0.0.0/cmd.exe.zip b/flows/codelingo/pull-request/bin/windows/amd64/0.0.0/cmd.exe.zip deleted file mode 100644 index 8255e6c8..00000000 Binary files a/flows/codelingo/pull-request/bin/windows/amd64/0.0.0/cmd.exe.zip and /dev/null differ diff --git a/flows/codelingo/pull-request/main.go b/flows/codelingo/pull-request/main.go deleted file mode 100644 index a650998b..00000000 --- a/flows/codelingo/pull-request/main.go +++ /dev/null @@ -1,106 +0,0 @@ -package flows - -import ( - "context" - "fmt" - - "github.com/codelingo/codelingo/flows/codelingo/review/review" - flowutil "github.com/codelingo/codelingo/sdk/flow" - "github.com/codelingo/lingo/app/util" - "github.com/codelingo/rpc/flow" - "github.com/juju/errors" - "github.com/urfave/cli" -) - -var pullRequestCmd = cli.Command{ - Name: "pull-request", - ShortName: "pr", - Usage: "review a remote pull-request", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: util.LingoFile.String(), - Usage: "A list of codelingo.yaml files to perform the review with. If the flag is not set, codelingo.yaml files are read from the branch being reviewed.", - }, - // TODO(waigani) as this is a review sub-command, it should be able to use the - // lingo-file flag from review. - // cli.BoolFlag{ - // Name: "all", - // Usage: "review all files under all directories from pwd down", - // }, - }, - Description: ` -"$ lingo review pull-request https://github.com/codelingo/lingo/pull/1" will review all code in the diff between the pull request and it's base repository. -"$ lingo review pr https://github.com/codelingo/lingo/pull/1" will review all code in the diff between the pull request and it's base repository. -`[1:], - // "$ lingo review" will review any unstaged changes from pwd down. - // "$ lingo review []" will review any unstaged changes in the named files. - // "$ lingo review --all []" will review all code in the named files. - Action: reviewPullRequestAction, -} - -func main() { - if err := flowutil.Run(pullRequestCmd); err != nil { - flowutil.HandleErr(err) - } -} - -func reviewPullRequestAction(ctx *cli.Context) { - msg, err := reviewPullRequestCMD(ctx) - if err != nil { - // Debugging - // print(errors.ErrorStack(err)) - util.FatalOSErr(err) - return - } - fmt.Println(msg) -} - -func reviewPullRequestCMD(cliCtx *cli.Context) (string, error) { - if l := len(cliCtx.Args()); l != 1 { - return "", errors.Errorf("expected one arg, got %d", l) - } - - dotlingo, err := review.ReadDotLingo(cliCtx) - if err != nil { - return "", errors.Trace(err) - } - - opts, err := review.ParsePR(cliCtx.Args()[0]) - if err != nil { - return "", errors.Trace(err) - } - - ctx, cancel := util.UserCancelContext(context.Background()) - issuec, errorc, err := review.RequestReview(ctx, &flow.ReviewRequest{ - Host: opts.Host, - Hostname: opts.HostName, - // TODO (Junyu) separate it into two separate fields - OwnerOrDepot: &flow.ReviewRequest_Owner{opts.Owner}, - Repo: opts.Name, - // Sha and patches are defined by the PR - IsPullRequest: true, - PullRequestID: int64(opts.PRID), - Dotlingo: dotlingo, - }) - if err != nil { - return "", errors.Trace(err) - } - - issues, err := review.ConfirmIssues(cancel, issuec, errorc, cliCtx.Bool("keep-all"), cliCtx.String("save")) - if err != nil { - return "", errors.Trace(err) - } - - // TODO: streaming back to the client, verify issues on the client side. - if len(issues) == 0 { - return "Done! No issues found.\n", nil - } - - msg, err := review.MakeReport(issues, cliCtx.String("format"), cliCtx.String("save")) - if err != nil { - return "", errors.Trace(err) - } - - fmt.Println(fmt.Printf("Done! Found %d issues \n", len(issues))) - return msg, nil -} diff --git a/flows/codelingo/review/bin/darwin/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/review/bin/darwin/amd64/0.0.0/cmd.tar.gz index 7738cd6c..4b2a933f 100644 Binary files a/flows/codelingo/review/bin/darwin/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/review/bin/darwin/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/review/bin/linux/386/0.0.0/cmd.tar.gz b/flows/codelingo/review/bin/linux/386/0.0.0/cmd.tar.gz deleted file mode 100644 index 484d7cc2..00000000 Binary files a/flows/codelingo/review/bin/linux/386/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/review/bin/linux/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/review/bin/linux/amd64/0.0.0/cmd.tar.gz index cf90ab4c..0025c033 100644 Binary files a/flows/codelingo/review/bin/linux/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/review/bin/linux/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/review/bin/windows/386/0.0.0/cmd.exe.zip b/flows/codelingo/review/bin/windows/386/0.0.0/cmd.exe.zip deleted file mode 100644 index 7bbad0e5..00000000 Binary files a/flows/codelingo/review/bin/windows/386/0.0.0/cmd.exe.zip and /dev/null differ diff --git a/flows/codelingo/review/bin/windows/amd64/0.0.0/cmd.exe.zip b/flows/codelingo/review/bin/windows/amd64/0.0.0/cmd.exe.zip index 3fa288ca..444146f0 100644 Binary files a/flows/codelingo/review/bin/windows/amd64/0.0.0/cmd.exe.zip and b/flows/codelingo/review/bin/windows/amd64/0.0.0/cmd.exe.zip differ diff --git a/flows/codelingo/review/main.go b/flows/codelingo/review/main.go index 81e9dc04..f94c9426 100644 --- a/flows/codelingo/review/main.go +++ b/flows/codelingo/review/main.go @@ -40,6 +40,7 @@ l: issue := result.Payload.(*flow.Issue) results = append(results, &review.ReportStrt{ + Name: issue.Name, Comment: issue.Comment, Filename: issue.Position.Start.Filename, Line: int(issue.Position.Start.Line), diff --git a/flows/codelingo/review/review/cli.go b/flows/codelingo/review/review/cli.go index 95acb91a..2031bb1d 100644 --- a/flows/codelingo/review/review/cli.go +++ b/flows/codelingo/review/review/cli.go @@ -3,7 +3,11 @@ package review import ( "context" "fmt" + "io/ioutil" "os" + "os/exec" + "path/filepath" + "strings" flowutil "github.com/codelingo/codelingo/sdk/flow" "github.com/codelingo/lingo/app/commands/verify" @@ -59,6 +63,14 @@ var CLIApp = &flowutil.CLIApp{ Hidden: true, Usage: "Review without TLS", }, + cli.StringFlag{ + Name: "repo", + Usage: "Review a repo directly, e.g. github.com/some/repo", + }, + cli.StringFlag{ + Name: "commit", + Usage: "With --repo, review a specific commit", + }, // cli.BoolFlag{ // Name: "all", // Usage: "review all files under all directories from pwd down", @@ -72,21 +84,29 @@ var CLIApp = &flowutil.CLIApp{ Request: reviewAction, } -func reviewRequire() error { - reqs := []verify.Require{verify.VCSRq, verify.HomeRq, verify.AuthRq, verify.ConfigRq, verify.VersionRq} +func alwaysRequire() error { + reqs := []verify.Require{verify.HomeRq, verify.ConfigRq, verify.VersionRq} for _, req := range reqs { - err := req.Verify() - if err != nil { + if err := req.Verify(); err != nil { return errors.Trace(err) } } return nil } -func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), error) { - err := reviewRequire() - if err != nil { - return nil, nil, nil, errors.Trace(err) +func noRepoRequire() error { + reqs := []verify.Require{verify.VCSRq, verify.AuthRq} + for _, req := range reqs { + if err := req.Verify(); err != nil { + return errors.Trace(err) + } + } + return nil +} + +func reviewAction(cliCtx *cli.Context) (chan proto.Message, <-chan *flowutil.UserVar, chan error, func(), error) { + if err := alwaysRequire(); err != nil { + return nil, nil, nil, nil, errors.Trace(err) } defer util.Logger.Sync() @@ -96,7 +116,7 @@ func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), dir := cliCtx.String("directory") if dir != "" { if err := os.Chdir(dir); err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } } @@ -105,41 +125,67 @@ func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), dotlingo, err := ReadDotLingo(cliCtx) if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) + } + + ctx, cancel := util.UserCancelContext(context.Background()) + if repo := cliCtx.String("repo"); repo != "" { + parts := strings.Split(repo, "/") + if len(parts) != 3 { + msg := "cannot parse repo; expected e.g. github.com/someuser/somerepo" + return nil, nil, nil, nil, errors.New(msg) + } + req := &flow.ReviewRequest{ + Vcs: "git", + Host: parts[0], + OwnerOrDepot: &flow.ReviewRequest_Owner{parts[1]}, + Repo: parts[2], + Sha: cliCtx.String("commit"), + Dotlingo: dotlingo, + } + + fmt.Println("Running review flow...") + resultc, userVarc, errc, err := RequestReview(ctx, req, insecure) + return resultc, userVarc, errc, cancel, errors.Trace(err) + } + + if err := noRepoRequire(); err != nil { + return nil, nil, nil, nil, errors.Trace(err) } + vcsType, repo, err := vcs.New() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } // TODO: replace this system with nfs-like communication. fmt.Println("Syncing your repo...") if err = vcs.SyncRepo(vcsType, repo); err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } owner, name, err := repo.OwnerAndNameFromRemote() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } sha, err := repo.CurrentCommitId() if err != nil { if flowutil.NoCommitErr(err) { - return nil, nil, nil, errors.New(flowutil.NoCommitErrMsg) + return nil, nil, nil, nil, errors.New(flowutil.NoCommitErrMsg) } - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } patches, err := repo.Patches() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } var patchesSize int64 @@ -152,22 +198,27 @@ func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), workingDir, err := repo.WorkingDir() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } cfg, err := config.Platform() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } vcsTypeStr, err := vcs.TypeToString(vcsType) if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) + + } + + path, err := findPath() + if err != nil { + return nil, nil, nil, nil, errors.Trace(err) } - ctx, cancel := util.UserCancelContext(context.Background()) req := &flow.ReviewRequest{ Repo: name, Sha: sha, @@ -175,17 +226,19 @@ func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), Vcs: vcsTypeStr, Dir: workingDir, Dotlingo: dotlingo, + Path: path, } + switch vcsTypeStr { case vcsGit: addr, err := cfg.GitServerAddr() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } hostname, err := cfg.GitRemoteName() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } @@ -195,17 +248,17 @@ func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), case vcsP4: addr, err := cfg.P4ServerAddr() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } hostname, err := cfg.P4RemoteName() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } depot, err := cfg.P4RemoteDepotName() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } name = owner + "/" + name @@ -215,10 +268,61 @@ func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), req.OwnerOrDepot = &flow.ReviewRequest_Depot{depot} req.Repo = name default: - return nil, nil, nil, errors.Errorf("Invalid VCS '%s'", vcsTypeStr) + return nil, nil, nil, nil, errors.Errorf("Invalid VCS '%s'", vcsTypeStr) } fmt.Println("Running review flow...") - resultc, errc, err := RequestReview(ctx, req, insecure) - return resultc, errc, cancel, errors.Trace(err) + resultc, userVarc, errc, err := RequestReview(ctx, req, insecure) + return resultc, userVarc, errc, cancel, errors.Trace(err) +} + +// `path` is the relative path from $GOPATH/src to the repo root. It's required for package resolution +// It is empty for non-Go repos +func findPath() (string, error) { + + // Find the root of repo + cmd := exec.Command("git", "rev-parse", "--show-toplevel") + output, err := cmd.CombinedOutput() + if err != nil { + return "", errors.Trace(err) + } + repoRoot := strings.TrimSpace(string(output)) + + // Check if the repo is a go project + goProject, err := isGoProject(repoRoot) + if err != nil { + return "", errors.Trace(err) + } + + // Check if GOPATH is set + goPath := os.Getenv("GOPATH") + + // Check if repo is in GOPATH + if goPath != "" && goProject { + if strings.HasPrefix(repoRoot, goPath+"/src/") { + return strings.TrimPrefix(repoRoot, goPath+"/src/"), nil + } + } + return "", nil +} + +func isGoProject(currentDir string) (bool, error) { + items, err := ioutil.ReadDir(currentDir) + if err != nil { + return false, errors.Trace(err) + } + for _, item := range items { + if !item.IsDir() && filepath.Ext(item.Name()) == ".go" { + return true, nil + } else if item.IsDir() && !strings.HasPrefix(item.Name(), ".") { + isGo, err := isGoProject(filepath.Join(currentDir, item.Name())) + if err != nil { + return false, errors.Trace(err) + } + if isGo { + return true, nil + } + } + } + return false, nil } diff --git a/flows/codelingo/review/review/decorator.go b/flows/codelingo/review/review/decorator.go index 5006984e..b1b74e50 100644 --- a/flows/codelingo/review/review/decorator.go +++ b/flows/codelingo/review/review/decorator.go @@ -19,7 +19,9 @@ var DecoratorApp = &flowutil.DecoratorApp{ Flags: []cli.Flag{}, }, ConfirmDecorated: decoratorAction, - + SetUserVar: func(v *flowutil.UserVar) { + v.Set() + }, // help info DecoratorUsage: "", DecoratorExample: `"this is a review comment"`, diff --git a/flows/codelingo/review/review/review.go b/flows/codelingo/review/review/review.go index facdcfa5..15928d6b 100644 --- a/flows/codelingo/review/review/review.go +++ b/flows/codelingo/review/review/review.go @@ -5,92 +5,38 @@ import ( "encoding/json" "fmt" "io/ioutil" - "sync" - - "github.com/golang/protobuf/ptypes" + flowutil "github.com/codelingo/codelingo/sdk/flow" "github.com/codelingo/lingo/app/util" - "github.com/codelingo/lingo/service" grpcclient "github.com/codelingo/lingo/service/grpc" "github.com/codelingo/rpc/flow" - "github.com/codelingo/rpc/flow/client" "github.com/golang/protobuf/proto" "github.com/juju/errors" "github.com/urfave/cli" ) -func RequestReview(ctx context.Context, req *flow.ReviewRequest, insecure bool) (chan proto.Message, chan error, error) { +func RequestReview(ctx context.Context, req *flow.ReviewRequest, insecure bool) (chan proto.Message, <-chan *flowutil.UserVar, chan error, error) { defer util.Logger.Sync() - util.Logger.Debug("opening connection to flow server ...") - conn, err := service.GrpcConnection(service.LocalClient, service.FlowServer, insecure) - if err != nil { - return nil, nil, errors.Trace(err) - } - util.Logger.Debug("...connection to flow server opened") - c := client.NewFlowClient(conn) // Create context with metadata - ctx, err = grpcclient.AddUsernameToCtx(ctx) - if err != nil { - return nil, nil, errors.Trace(err) - } - - payload, err := ptypes.MarshalAny(req) + ctx, err := grpcclient.AddUsernameToCtx(ctx) if err != nil { - return nil, nil, errors.Trace(err) + return nil, nil, nil, errors.Trace(err) } util.Logger.Debug("sending request to flow server...") - replyc, runErrc, err := c.Run(ctx, &flow.Request{Flow: "review", Payload: payload}) + issuec, userVarc, errc, _, err := flowutil.RunFlow("review", req, + func() proto.Message { return &flow.Issue{} }, + func(m proto.Message) proto.Message { return m }) if err != nil { - return nil, nil, errors.Trace(err) + return nil, nil, nil, errors.Trace(err) } util.Logger.Debug("...request to flow server sent. Received reply channel.") - - issuec := make(chan proto.Message) - errc := make(chan error) - wg := sync.WaitGroup{} - wg.Add(2) - - go func() { - for err := range runErrc { - errc <- err - } - wg.Done() - }() - - go func() { - for reply := range replyc { - if reply.IsHeartbeat { - continue - } - if reply.Error != "" { - errc <- errors.New(reply.Error) - continue - } - - issue := &flow.Issue{} - err := ptypes.UnmarshalAny(reply.Payload, issue) - if err != nil { - errc <- err - continue - } - - issuec <- issue - } - wg.Done() - }() - - go func() { - wg.Wait() - close(issuec) - close(errc) - }() - - return issuec, errc, nil + return issuec, userVarc, errc, nil } type ReportStrt struct { + Name string Comment string Filename string Line int diff --git a/flows/codelingo/rewrite/bin/darwin/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/rewrite/bin/darwin/amd64/0.0.0/cmd.tar.gz index bb68c8f7..52c7795e 100644 Binary files a/flows/codelingo/rewrite/bin/darwin/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/rewrite/bin/darwin/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/rewrite/bin/linux/386/0.0.0/cmd.tar.gz b/flows/codelingo/rewrite/bin/linux/386/0.0.0/cmd.tar.gz deleted file mode 100644 index 314adacc..00000000 Binary files a/flows/codelingo/rewrite/bin/linux/386/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/rewrite/bin/linux/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/rewrite/bin/linux/amd64/0.0.0/cmd.tar.gz index 7ea9b488..d3e7c48b 100644 Binary files a/flows/codelingo/rewrite/bin/linux/amd64/0.0.0/cmd.tar.gz and b/flows/codelingo/rewrite/bin/linux/amd64/0.0.0/cmd.tar.gz differ diff --git a/flows/codelingo/rewrite/bin/windows/386/0.0.0/cmd.exe.zip b/flows/codelingo/rewrite/bin/windows/386/0.0.0/cmd.exe.zip deleted file mode 100644 index be722b75..00000000 Binary files a/flows/codelingo/rewrite/bin/windows/386/0.0.0/cmd.exe.zip and /dev/null differ diff --git a/flows/codelingo/rewrite/bin/windows/amd64/0.0.0/cmd.exe.zip b/flows/codelingo/rewrite/bin/windows/amd64/0.0.0/cmd.exe.zip index 7d5fe585..822d4453 100644 Binary files a/flows/codelingo/rewrite/bin/windows/amd64/0.0.0/cmd.exe.zip and b/flows/codelingo/rewrite/bin/windows/amd64/0.0.0/cmd.exe.zip differ diff --git a/flows/codelingo/rewrite/rewrite/cli.go b/flows/codelingo/rewrite/rewrite/cli.go index 20289c0b..38d5ebec 100644 --- a/flows/codelingo/rewrite/rewrite/cli.go +++ b/flows/codelingo/rewrite/rewrite/cli.go @@ -50,6 +50,10 @@ var CLIApp = &flowutil.CLIApp{ Name: "debug", Usage: "Display debug messages", }, + cli.StringFlag{ + Name: "dump-comments, c", + Usage: "Output JSON dump of interactive comments to a given file.", + }, }, }, Request: rewriteAction, @@ -67,11 +71,11 @@ func rewriteRequire() error { return nil } -func rewriteAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), error) { +func rewriteAction(cliCtx *cli.Context) (chan proto.Message, <-chan *flowutil.UserVar, chan error, func(), error) { err := rewriteRequire() if err != nil { util.FatalOSErr(err) - return nil, nil, nil, err + return nil, nil, nil, nil, err } defer util.Logger.Sync() @@ -81,75 +85,83 @@ func rewriteAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), dir := cliCtx.String("directory") if dir != "" { if err := os.Chdir(dir); err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } } dotlingo, err := flowutil.ReadDotLingo(cliCtx) if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } vcsType, repo, err := vcs.New() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } // TODO: replace this system with nfs-like communication. fmt.Println("Syncing your repo...") if err = vcs.SyncRepo(vcsType, repo); err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } owner, name, err := repo.OwnerAndNameFromRemote() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } sha, err := repo.CurrentCommitId() if err != nil { if flowutil.NoCommitErr(err) { - return nil, nil, nil, errors.New(flowutil.NoCommitErrMsg) + return nil, nil, nil, nil, errors.New(flowutil.NoCommitErrMsg) } - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } patches, err := repo.Patches() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } workingDir, err := repo.WorkingDir() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } cfg, err := config.Platform() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } vcsTypeStr, err := vcs.TypeToString(vcsType) if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) + } + + var generateComments bool + commentOutputFile := "" + if cliCtx.IsSet("dump-comments") { + generateComments = true + commentOutputFile = cliCtx.String("dump-comments") } req := &rewriterpc.Request{ - Repo: name, - Sha: sha, - Patches: patches, - Vcs: vcsTypeStr, - Dir: workingDir, - Dotlingo: dotlingo, + Repo: name, + Sha: sha, + Patches: patches, + Vcs: vcsTypeStr, + Dir: workingDir, + Dotlingo: dotlingo, + GenerateComments: generateComments, } switch vcsTypeStr { case vcsGit: addr, err := cfg.GitServerAddr() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } hostname, err := cfg.GitRemoteName() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } req.Host = addr @@ -158,15 +170,15 @@ func rewriteAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), case vcsP4: addr, err := cfg.P4ServerAddr() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } hostname, err := cfg.P4RemoteName() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } depot, err := cfg.P4RemoteDepotName() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } name = owner + "/" + name @@ -175,11 +187,17 @@ func rewriteAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), req.OwnerOrDepot = &rewriterpc.Request_Depot{depot} req.Repo = name default: - return nil, nil, nil, errors.Errorf("Invalid VCS '%s'", vcsTypeStr) + return nil, nil, nil, nil, errors.Errorf("Invalid VCS '%s'", vcsTypeStr) } fmt.Println("Running rewrite flow...") - // proto.RegisterType((*rewriterpc.Hunk)(nil), "rpc.Hunk") - return flowutil.RunFlow("rewrite", req, func() proto.Message { return &rewriterpc.Hunk{} }) + return flowutil.RunFlow("rewrite", req, + func() proto.Message { return &rewriterpc.Hunk{} }, + func(unmarshalled proto.Message) proto.Message { + hunk := unmarshalled.(*rewriterpc.Hunk) + hunk.CommentOutputFile = commentOutputFile + return hunk + }, + ) } diff --git a/flows/codelingo/rewrite/rewrite/decorator.go b/flows/codelingo/rewrite/rewrite/decorator.go index 981cf04b..35da9420 100644 --- a/flows/codelingo/rewrite/rewrite/decorator.go +++ b/flows/codelingo/rewrite/rewrite/decorator.go @@ -59,6 +59,9 @@ var DecoratorApp = &flowutil.DecoratorApp{ }, }, ConfirmDecorated: decoratorAction, + SetUserVar: func(v *flowutil.UserVar) { + v.Set() + }, DecoratorUsage: "[options] ", DecoratorExample: `--prepend --line "// new comment on a Golang function`, } diff --git a/flows/codelingo/rewrite/rewrite/writer.go b/flows/codelingo/rewrite/rewrite/writer.go index 885a6f5f..308ce8ca 100644 --- a/flows/codelingo/rewrite/rewrite/writer.go +++ b/flows/codelingo/rewrite/rewrite/writer.go @@ -1,6 +1,7 @@ package rewrite import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -31,6 +32,8 @@ func Write(results []*flowutil.DecoratedResult) error { } seenNewFile := make(map[string]bool) + var comments []*comment + var commentOutputFile string for filename, results := range resultMap { @@ -54,6 +57,7 @@ func Write(results []*flowutil.DecoratedResult) error { ctx := result.Ctx hunk := result.Payload.(*rewriterpc.Hunk) + commentOutputFile = hunk.CommentOutputFile if ctx.IsSet("new-file") { @@ -76,11 +80,16 @@ func Write(results []*flowutil.DecoratedResult) error { continue } - fileSRC, err = newFileSRC(ctx, hunk, fileSRC) + var comment *comment + fileSRC, comment, err = newFileSRC(ctx, hunk, fileSRC) if err != nil { return errors.Trace(err) } + if comment != nil { + comment.Path = fullPath + comments = append(comments, comment) + } } if err := ioutil.WriteFile(fullPath, []byte(fileSRC), 0644); err != nil { @@ -90,6 +99,20 @@ func Write(results []*flowutil.DecoratedResult) error { } + if commentOutputFile != "" && len(comments) > 0 { + fmt.Println("writing file to", commentOutputFile) + output, err := json.Marshal(comments) + if err != nil { + return errors.Trace(err) + } + + // TODO: read filepath from flag + err = ioutil.WriteFile(commentOutputFile, output, 0644) + if err != nil { + return errors.Trace(err) + } + } + return nil } @@ -114,94 +137,162 @@ func lineOffsets(src []byte, offset int32) []int32 { return []int32{start, end} } -func newFileSRC(ctx *cli.Context, hunk *rewriterpc.Hunk, fileSRC []byte) ([]byte, error) { +type partitionedFile struct { + srcBeforeStartOffset func() []byte + srcAfterStartOffset func() []byte + srcBeforeEndOffset func() []byte + srcAfterEndOffset func() []byte - opts, err := option.New(ctx) - if err != nil { - return nil, errors.Trace(err) - } + srcBeforeStartLine func() []byte + srcAfterStartLine func() []byte + srcBeforeEndLine func() []byte + srcAfterEndLine func() []byte - newSRC := []byte(hunk.SRC) - newLine := append(newSRC, '\n') - newLineAfter := append([]byte{'\n'}, newSRC...) + startLineOffsets func() []int32 + endLineOffsets func() []int32 +} +func splitSRC(hunk *rewriterpc.Hunk, fileSRC []byte) partitionedFile { startLineOffsets := lineOffsets(fileSRC, hunk.StartOffset) endLineOffsets := lineOffsets(fileSRC, hunk.EndOffset) - srcBeforeStartOffset := fileSRC[0:hunk.StartOffset] - srcAfterStartOffset := fileSRC[hunk.StartOffset+1:] - srcBeforeEndOffset := fileSRC[0 : hunk.EndOffset-1] - srcAfterEndOffset := fileSRC[hunk.EndOffset:] + return partitionedFile{ + srcBeforeStartOffset: func() []byte { return []byte(string(fileSRC))[0:hunk.StartOffset] }, + srcAfterStartOffset: func() []byte { return []byte(string(fileSRC))[hunk.StartOffset+1:] }, + srcBeforeEndOffset: func() []byte { return []byte(string(fileSRC))[0 : hunk.EndOffset-1] }, + srcAfterEndOffset: func() []byte { return []byte(string(fileSRC))[hunk.EndOffset:] }, + + srcBeforeStartLine: func() []byte { return []byte(string(fileSRC))[0:startLineOffsets[0]] }, + srcAfterStartLine: func() []byte { return []byte(string(fileSRC))[startLineOffsets[1]+1:] }, + srcBeforeEndLine: func() []byte { return []byte(string(fileSRC))[0:endLineOffsets[0]] }, + srcAfterEndLine: func() []byte { return []byte(string(fileSRC))[endLineOffsets[1]+1:] }, + + startLineOffsets: func() []int32 { return startLineOffsets }, + endLineOffsets: func() []int32 { return endLineOffsets }, + } +} + +type comment struct { + Content string `json:"content"` + Original string `json:"original"` + // TODO: comments should span multiple lines, but github doesn't allow that https://github.community/t5/How-to-use-Git-and-GitHub/Feature-request-Multiline-reviews-in-pull-requests/m-p/9850#M3225 + Line int `json:"line"` + Path string `json:"path"` +} + +func newFileSRC(ctx *cli.Context, hunk *rewriterpc.Hunk, fileSRC []byte) ([]byte, *comment, error) { + parts := splitSRC(hunk, fileSRC) + fileSRClines := strings.Split(string(fileSRC), "\n") - srcBeforeStartLine := fileSRC[0:startLineOffsets[0]] - srcAfterStartLine := fileSRC[startLineOffsets[1]+1:] - srcBeforeEndLine := fileSRC[0:endLineOffsets[0]] - srcAfterEndLine := fileSRC[endLineOffsets[1]+1:] + rewrittenFile, err := rewriteFile(ctx, fileSRC, []byte(hunk.SRC), parts, hunk) + if err != nil { + return nil, nil, errors.Trace(err) + } + + var c *comment + if hunk.Comment != "" { + commentedSRC, err := rewriteFile(ctx, fileSRC, []byte(hunk.Comment), parts, hunk) + if err != nil { + return nil, nil, errors.Trace(err) + } + + // Find updated line in new rewrittenFile + commentedLines := strings.Split(string(commentedSRC), "\n") + for lineNumber, updatedLine := range strings.Split(string(rewrittenFile), "\n") { + if len(commentedSRC) <= lineNumber { + return nil, nil, errors.New("reached end of commented file before finding updated line") + } + + commentedLine := commentedLines[lineNumber] + if updatedLine != commentedLine { + c = &comment{ + Content: string(commentedLine), + Original: fileSRClines[lineNumber], + Line: lineNumber + 1, + } + break + } + } + } + return rewrittenFile, c, nil +} + +func rewriteFile(ctx *cli.Context, inputSRC, newSRC []byte, parts partitionedFile, hunk *rewriterpc.Hunk) ([]byte, error) { + fileSRC := []byte(string(inputSRC)) + + opts, err := option.New(ctx) + if err != nil { + return nil, errors.Trace(err) + } + newLine := append(newSRC, '\n') + newLineAfter := append([]byte{'\n'}, newSRC...) switch { case opts.IsReplace() && opts.IsStartToEndOffset() && opts.IsByte(): // replace between start and end bytes - fileSRC = append(srcBeforeStartOffset, append(newSRC, srcAfterEndOffset...)...) + fileSRC = append(parts.srcBeforeStartOffset(), append(newSRC, parts.srcAfterEndOffset()...)...) case opts.IsReplace() && opts.IsStartOffset() && opts.IsByte(): // replace only the start byte - fileSRC = append(srcBeforeStartOffset, append(newSRC, fileSRC[hunk.StartOffset+1:]...)...) + fileSRC = append(parts.srcBeforeStartOffset(), append(newSRC, parts.srcAfterStartOffset()...)...) case opts.IsReplace() && opts.IsEndOffset() && opts.IsByte(): // replace only the end byte - fileSRC = append(fileSRC[0:hunk.EndOffset-1], append(newSRC, srcAfterEndOffset...)...) + fileSRC = append(parts.srcBeforeEndOffset(), append(newSRC, parts.srcAfterEndOffset()...)...) case opts.IsReplace() && opts.IsStartToEndOffset() && opts.IsLine(): - fileSRC = append(srcBeforeStartLine, append(newSRC, srcAfterEndLine...)...) - + // o.Do(func() { + fileSRC = append(parts.srcBeforeStartLine(), append(newSRC, parts.srcAfterEndLine()...)...) + // }) case opts.IsReplace() && opts.IsStartOffset() && opts.IsLine(): - fileSRC = append(srcBeforeStartLine, append(newSRC, srcAfterStartLine...)...) + fileSRC = append(parts.srcBeforeStartLine(), append(newSRC, parts.srcAfterStartLine()...)...) case opts.IsReplace() && opts.IsEndOffset() && opts.IsLine(): // replace whole line - fileSRC = append(srcBeforeEndLine, append(newSRC, srcAfterEndLine...)...) + fileSRC = append(parts.srcBeforeEndLine(), append(newSRC, parts.srcAfterEndLine()...)...) case opts.IsPrepend() && opts.IsStartToEndOffset() && opts.IsByte(): fallthrough case opts.IsPrepend() && opts.IsStartOffset() && opts.IsByte(): // insert before startoffset - fileSRC = append(srcBeforeStartOffset, append(newSRC, fileSRC[hunk.StartOffset:]...)...) - + // TODO: remove reference to hunk + fileSRC = append(parts.srcBeforeStartOffset(), append(newSRC, fileSRC[hunk.StartOffset:]...)...) case opts.IsPrepend() && opts.IsEndOffset() && opts.IsByte(): // insert before endoffset - fileSRC = append(srcBeforeEndOffset, append(newSRC, fileSRC[hunk.EndOffset-1:]...)...) + fileSRC = append(parts.srcBeforeEndOffset(), append(newSRC, fileSRC[hunk.EndOffset-1:]...)...) case opts.IsPrepend() && opts.IsStartToEndOffset() && opts.IsLine(): fallthrough case opts.IsPrepend() && opts.IsStartOffset() && opts.IsLine(): // insert on new line above startoffset - fileSRC = append(srcBeforeStartLine, append(newLine, fileSRC[startLineOffsets[0]:]...)...) + fileSRC = append(parts.srcBeforeStartLine(), append(newLine, fileSRC[parts.startLineOffsets()[0]:]...)...) case opts.IsPrepend() && opts.IsEndOffset() && opts.IsLine(): // insert on new line above endoffset - fileSRC = append(srcBeforeEndLine, append(newLine, fileSRC[endLineOffsets[0]:]...)...) + fileSRC = append(parts.srcBeforeEndLine(), append(newLine, fileSRC[parts.endLineOffsets()[0]:]...)...) case opts.IsAppend() && opts.IsStartToEndOffset() && opts.IsByte(): fallthrough case opts.IsAppend() && opts.IsEndOffset() && opts.IsByte(): // insert after endoffset - fileSRC = append(fileSRC[0:hunk.EndOffset], append(newSRC, srcAfterEndOffset...)...) + fileSRC = append(fileSRC[0:hunk.EndOffset], append(newSRC, parts.srcAfterEndOffset()...)...) case opts.IsAppend() && opts.IsStartOffset() && opts.IsByte(): // insert after startoffset - fileSRC = append(fileSRC[0:hunk.StartOffset+1], append(newSRC, srcAfterStartOffset...)...) + fileSRC = append(fileSRC[0:hunk.StartOffset+1], append(newSRC, parts.srcAfterStartOffset()...)...) case opts.IsAppend() && opts.IsStartToEndOffset() && opts.IsLine(): fallthrough case opts.IsAppend() && opts.IsEndOffset() && opts.IsLine(): // insert on new line after endoffset - fileSRC = append(fileSRC[0:endLineOffsets[1]+1], append(newLineAfter, srcAfterEndLine...)...) + fileSRC = append(fileSRC[0:parts.endLineOffsets()[1]+1], append(newLineAfter, parts.srcAfterEndLine()...)...) case opts.IsAppend() && opts.IsStartOffset() && opts.IsLine(): // insert on new line after startoffset - fileSRC = append(fileSRC[0:startLineOffsets[1]+1], append(newLineAfter, srcAfterStartLine...)...) + fileSRC = append(fileSRC[0:parts.startLineOffsets()[1]+1], append(newLineAfter, parts.srcAfterStartLine()...)...) + default: + return nil, errors.New("rewrite case not found") } - return fileSRC, nil } diff --git a/flows/codelingo/rewrite/rewrite/writer_test.go b/flows/codelingo/rewrite/rewrite/writer_test.go index 03bc0752..9783d558 100644 --- a/flows/codelingo/rewrite/rewrite/writer_test.go +++ b/flows/codelingo/rewrite/rewrite/writer_test.go @@ -39,7 +39,7 @@ func (s *cmdSuite) TestNewFile(c *gc.C) { newFile := "new_test.go" - ctx, err := flowutil.NewCtx(DecoratorCMD.Command, "--new-file", newFile, "--new-file-perm", "0755") + ctx, err := flowutil.NewCtx(&DecoratorApp.App, "--new-file", newFile, "--new-file-perm", "0755") c.Assert(err, jc.ErrorIsNil) results := []*flowutil.DecoratedResult{ @@ -64,28 +64,32 @@ func (s *cmdSuite) TestNewFile(c *gc.C) { func (s *cmdSuite) TestNewFileSRC(c *gc.C) { - for _, data := range testData { - + for _, test := range testData { hunk := &rewriterpc.Hunk{ SRC: "", StartOffset: int32(19), EndOffset: int32(23), - DecoratorOptions: data.decOpts, + DecoratorOptions: test.decOpts, Filename: "not_used", + Comment: "", + } + if test.overWriteComment { + hunk.SRC = test.commentValueOverwrite } ctx, err := flowutil.NewCtx(&DecoratorApp.App, strings.Split(hunk.DecoratorOptions, " ")[1:]...) c.Assert(err, jc.ErrorIsNil) - newCode, err := newFileSRC(ctx, hunk, []byte(oldSRC)) + newCode, comment, err := newFileSRC(ctx, hunk, []byte(oldSRC)) c.Assert(err, jc.ErrorIsNil) - c.Assert(string(newCode), gc.Equals, string(data.newSRC)) - fmt.Println("PASS:", data.decOpts) + c.Assert(string(newCode), gc.Equals, string(test.newSRC)) + c.Assert(comment, gc.DeepEquals, test.comment) + fmt.Println("PASS:", test.decOpts) } } -var oldSRC string = ` +var oldSRC = ` package test func main() { @@ -94,11 +98,19 @@ func main() { `[1:] var testData = []struct { - decOpts string - newSRC []byte + decOpts string + newSRC []byte + comment *comment + commentValueOverwrite string + overWriteComment bool }{ { decOpts: "rewrite \"\"", + comment: &comment{ + Line: 3, + Content: "func () {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -108,15 +120,41 @@ func () { `[1:]), }, { decOpts: "rewrite name", + comment: &comment{ + Line: 3, + Content: "func () {", + Original: "func main() {", + }, newSRC: []byte(` package test func () { +} +`[1:]), + }, { + decOpts: "rewrite --replace --line", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, + overWriteComment: true, + commentValueOverwrite: "", + newSRC: []byte(` +package test + + + } `[1:]), }, { decOpts: "rewrite --replace name", + comment: &comment{ + Line: 3, + Content: "func () {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -126,6 +164,11 @@ func () { `[1:]), }, { decOpts: "rewrite --replace --start-to-end-offset name", + comment: &comment{ + Line: 3, + Content: "func () {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -135,6 +178,11 @@ func () { `[1:]), }, { decOpts: "rewrite --start-to-end-offset name", + comment: &comment{ + Line: 3, + Content: "func () {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -144,6 +192,11 @@ func () { `[1:]), }, { decOpts: "rewrite --start-offset name", + comment: &comment{ + Line: 3, + Content: "func ain() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -153,6 +206,11 @@ func ain() { `[1:]), }, { decOpts: "rewrite --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -162,6 +220,11 @@ package test `[1:]), }, { decOpts: "rewrite --start-offset --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -171,6 +234,11 @@ package test `[1:]), }, { decOpts: "rewrite --end-offset --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -180,6 +248,11 @@ package test `[1:]), }, { decOpts: "rewrite --start-to-end-offset --prepend name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -189,6 +262,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-offset --prepend name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -198,6 +276,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --prepend name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -207,6 +290,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --end-offset --prepend name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -216,6 +304,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --prepend --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -226,6 +319,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-to-end-offset --prepend --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -236,6 +334,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-offset --prepend --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -246,6 +349,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --end-offset --prepend --line name", + comment: &comment{ + Line: 3, + Content: "", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -256,6 +364,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --append name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -265,6 +378,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-to-end-offset --append name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -274,6 +392,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-offset --append name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -283,6 +406,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --end-offset --append name", + comment: &comment{ + Line: 3, + Content: "func main() {", + Original: "func main() {", + }, newSRC: []byte(` package test @@ -292,6 +420,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --append --line name", + comment: &comment{ + Line: 4, + Content: "", + Original: "", + }, newSRC: []byte(` package test @@ -302,6 +435,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-to-end-offset --append --line name", + comment: &comment{ + Line: 4, + Content: "", + Original: "", + }, newSRC: []byte(` package test @@ -312,6 +450,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --end-offset --append --line name", + comment: &comment{ + Line: 4, + Content: "", + Original: "", + }, newSRC: []byte(` package test @@ -322,6 +465,11 @@ func main() { `[1:]), }, { decOpts: "rewrite --start-offset --append --line name", + comment: &comment{ + Line: 4, + Content: "", + Original: "", + }, newSRC: []byte(` package test diff --git a/flows/codelingo/rewrite/rpc/rewrite.pb.go b/flows/codelingo/rewrite/rpc/rewrite.pb.go index e0f87c18..50548047 100644 --- a/flows/codelingo/rewrite/rpc/rewrite.pb.go +++ b/flows/codelingo/rewrite/rpc/rewrite.pb.go @@ -31,20 +31,21 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // The request message containing the files or directories to review. type Request struct { - Host string `protobuf:"bytes,1,opt,name=host" json:"host,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname" json:"hostname,omitempty"` + Host string `protobuf:"bytes,1,opt,name=host" json:"host,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname" json:"hostname,omitempty"` + GenerateComments bool `protobuf:"varint,3,opt,name=generateComments" json:"generateComments,omitempty"` + Repo string `protobuf:"bytes,4,opt,name=repo" json:"repo,omitempty"` + Sha string `protobuf:"bytes,5,opt,name=sha" json:"sha,omitempty"` + Patches []string `protobuf:"bytes,6,rep,name=Patches" json:"Patches,omitempty"` + IsPullRequest bool `protobuf:"varint,7,opt,name=isPullRequest" json:"isPullRequest,omitempty"` + PullRequestID int64 `protobuf:"varint,8,opt,name=pullRequestID" json:"pullRequestID,omitempty"` + Vcs string `protobuf:"bytes,9,opt,name=vcs" json:"vcs,omitempty"` + Dotlingo string `protobuf:"bytes,10,opt,name=dotlingo" json:"dotlingo,omitempty"` + Dir string `protobuf:"bytes,11,opt,name=dir" json:"dir,omitempty"` // Types that are valid to be assigned to OwnerOrDepot: // *Request_Owner // *Request_Depot - OwnerOrDepot isRequest_OwnerOrDepot `protobuf_oneof:"ownerOrDepot"` - Repo string `protobuf:"bytes,4,opt,name=repo" json:"repo,omitempty"` - Sha string `protobuf:"bytes,5,opt,name=sha" json:"sha,omitempty"` - Patches []string `protobuf:"bytes,6,rep,name=Patches" json:"Patches,omitempty"` - IsPullRequest bool `protobuf:"varint,7,opt,name=isPullRequest" json:"isPullRequest,omitempty"` - PullRequestID int64 `protobuf:"varint,8,opt,name=pullRequestID" json:"pullRequestID,omitempty"` - Vcs string `protobuf:"bytes,9,opt,name=vcs" json:"vcs,omitempty"` - Dotlingo string `protobuf:"bytes,10,opt,name=dotlingo" json:"dotlingo,omitempty"` - Dir string `protobuf:"bytes,11,opt,name=dir" json:"dir,omitempty"` + OwnerOrDepot isRequest_OwnerOrDepot `protobuf_oneof:"ownerOrDepot"` } func (m *Request) Reset() { *m = Request{} } @@ -87,18 +88,11 @@ func (m *Request) GetHostname() string { return "" } -func (m *Request) GetOwner() string { - if x, ok := m.GetOwnerOrDepot().(*Request_Owner); ok { - return x.Owner - } - return "" -} - -func (m *Request) GetDepot() string { - if x, ok := m.GetOwnerOrDepot().(*Request_Depot); ok { - return x.Depot +func (m *Request) GetGenerateComments() bool { + if m != nil { + return m.GenerateComments } - return "" + return false } func (m *Request) GetRepo() string { @@ -157,6 +151,20 @@ func (m *Request) GetDir() string { return "" } +func (m *Request) GetOwner() string { + if x, ok := m.GetOwnerOrDepot().(*Request_Owner); ok { + return x.Owner + } + return "" +} + +func (m *Request) GetDepot() string { + if x, ok := m.GetOwnerOrDepot().(*Request_Depot); ok { + return x.Depot + } + return "" +} + // XXX_OneofFuncs is for the internal use of the proto package. func (*Request) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { return _Request_OneofMarshaler, _Request_OneofUnmarshaler, _Request_OneofSizer, []interface{}{ @@ -224,11 +232,14 @@ func _Request_OneofSizer(msg proto.Message) (n int) { } type Hunk struct { - Filename string `protobuf:"bytes,1,opt,name=filename" json:"filename,omitempty"` - StartOffset int32 `protobuf:"varint,2,opt,name=startOffset" json:"startOffset,omitempty"` - EndOffset int32 `protobuf:"varint,3,opt,name=endOffset" json:"endOffset,omitempty"` - SRC string `protobuf:"bytes,4,opt,name=SRC" json:"SRC,omitempty"` - DecoratorOptions string `protobuf:"bytes,5,opt,name=decoratorOptions" json:"decoratorOptions,omitempty"` + Filename string `protobuf:"bytes,1,opt,name=filename" json:"filename,omitempty"` + StartOffset int32 `protobuf:"varint,2,opt,name=startOffset" json:"startOffset,omitempty"` + EndOffset int32 `protobuf:"varint,3,opt,name=endOffset" json:"endOffset,omitempty"` + SRC string `protobuf:"bytes,4,opt,name=SRC" json:"SRC,omitempty"` + Comment string `protobuf:"bytes,5,opt,name=comment" json:"comment,omitempty"` + // Hack to pass flag from CLIApp to writer + CommentOutputFile string `protobuf:"bytes,6,opt,name=commentOutputFile" json:"commentOutputFile,omitempty"` + DecoratorOptions string `protobuf:"bytes,7,opt,name=decoratorOptions" json:"decoratorOptions,omitempty"` } func (m *Hunk) Reset() { *m = Hunk{} } @@ -264,6 +275,20 @@ func (m *Hunk) GetSRC() string { return "" } +func (m *Hunk) GetComment() string { + if m != nil { + return m.Comment + } + return "" +} + +func (m *Hunk) GetCommentOutputFile() string { + if m != nil { + return m.CommentOutputFile + } + return "" +} + func (m *Hunk) GetDecoratorOptions() string { if m != nil { return m.DecoratorOptions @@ -281,29 +306,32 @@ func init() { } var fileDescriptor0 = []byte{ - // 382 bytes of a gzipped FileDescriptorProto + // 427 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x92, 0xcf, 0x8a, 0xdb, 0x30, - 0x10, 0xc6, 0xeb, 0x38, 0xd9, 0xc4, 0xda, 0x3f, 0x2c, 0xa2, 0x14, 0x51, 0x7a, 0x30, 0x4b, 0x0f, - 0xa6, 0x07, 0xef, 0xa1, 0x6f, 0x90, 0xec, 0x21, 0x85, 0x40, 0x8c, 0x7a, 0xe8, 0xd9, 0x91, 0xe4, - 0x58, 0xd4, 0xf1, 0xa8, 0x92, 0x9c, 0xbc, 0x4e, 0xe9, 0xbb, 0xf4, 0xbd, 0xca, 0xc8, 0x76, 0xfe, - 0xd0, 0x93, 0xbf, 0xef, 0x37, 0xc3, 0x58, 0xf3, 0x31, 0x64, 0xbd, 0xd7, 0xbe, 0xee, 0x76, 0xb9, - 0x80, 0xc3, 0xab, 0x00, 0xa9, 0x1a, 0xdd, 0xee, 0xe1, 0x4a, 0x55, 0x0d, 0x9c, 0xdc, 0x95, 0xb7, - 0xea, 0x64, 0xb5, 0x57, 0xaf, 0xd6, 0x88, 0x51, 0xe7, 0xc6, 0x82, 0x07, 0x1a, 0x5b, 0x23, 0x5e, - 0xfe, 0x4e, 0xc8, 0x9c, 0xab, 0x5f, 0x9d, 0x72, 0x9e, 0x52, 0x32, 0xad, 0xc1, 0x79, 0x16, 0xa5, - 0x51, 0x96, 0xf0, 0xa0, 0xe9, 0x47, 0xb2, 0xc0, 0x6f, 0x5b, 0x1e, 0x14, 0x9b, 0x04, 0x7e, 0xf6, - 0xf4, 0x03, 0x99, 0xc1, 0xa9, 0x55, 0x96, 0x3d, 0x60, 0x61, 0xfd, 0x8e, 0xf7, 0x16, 0xb9, 0x54, - 0x06, 0x3c, 0x7b, 0x1c, 0x79, 0xb0, 0x38, 0xdf, 0x2a, 0x03, 0x6c, 0xda, 0xcf, 0x47, 0x4d, 0x9f, - 0x49, 0xec, 0xea, 0x92, 0xcd, 0x02, 0x42, 0x49, 0x19, 0x99, 0x17, 0xa5, 0x17, 0xb5, 0x72, 0xec, - 0x2e, 0x8d, 0xb3, 0x84, 0x8f, 0x96, 0x7e, 0x26, 0x8f, 0xda, 0x15, 0x5d, 0xd3, 0x0c, 0x0f, 0x66, - 0xf3, 0x34, 0xca, 0x16, 0xfc, 0x16, 0x62, 0x97, 0xb9, 0xd8, 0x6f, 0x6f, 0x6c, 0x91, 0x46, 0x59, - 0xcc, 0x6f, 0x21, 0xfe, 0xf7, 0x28, 0x1c, 0x4b, 0xfa, 0xff, 0x1e, 0x85, 0xc3, 0x4d, 0x25, 0xf8, - 0x10, 0x1a, 0x23, 0xfd, 0xa6, 0xa3, 0xc7, 0x6e, 0xa9, 0x2d, 0xbb, 0xef, 0xbb, 0xa5, 0xb6, 0xcb, - 0x27, 0xf2, 0x10, 0x96, 0xdd, 0xda, 0x37, 0xdc, 0xed, 0xe5, 0x77, 0x44, 0xa6, 0xeb, 0xae, 0xfd, - 0x89, 0x63, 0x2a, 0xdd, 0xa8, 0x10, 0x58, 0x1f, 0xe4, 0xd9, 0xd3, 0x94, 0xdc, 0x3b, 0x5f, 0x5a, - 0xbf, 0xad, 0x2a, 0xa7, 0x7c, 0xc8, 0x73, 0xc6, 0xaf, 0x11, 0xfd, 0x44, 0x12, 0xd5, 0xca, 0xa1, - 0x1e, 0x87, 0xfa, 0x05, 0xe0, 0x33, 0xbe, 0xf3, 0xd5, 0x90, 0x1f, 0x4a, 0xfa, 0x85, 0x3c, 0x4b, - 0x25, 0xc0, 0x96, 0x1e, 0xec, 0xd6, 0x78, 0x0d, 0xad, 0x1b, 0xb2, 0xfc, 0x8f, 0x2f, 0x73, 0xf2, - 0x5e, 0x43, 0x7e, 0xbe, 0x8c, 0x7c, 0x6f, 0x8d, 0xc8, 0x45, 0xb3, 0x7c, 0x5a, 0x81, 0x54, 0x1b, - 0x44, 0x05, 0xde, 0x45, 0x11, 0xfd, 0x99, 0xc4, 0xab, 0xcd, 0x8f, 0xdd, 0x5d, 0x38, 0x93, 0xaf, - 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xec, 0x4c, 0xd1, 0xb1, 0x72, 0x02, 0x00, 0x00, + 0x10, 0xc6, 0xeb, 0x38, 0xff, 0xac, 0xfd, 0xc3, 0x56, 0x94, 0x22, 0x4a, 0x0f, 0x66, 0xe9, 0x21, + 0x94, 0xe2, 0x3d, 0xf4, 0x0d, 0x92, 0xa5, 0xa4, 0xb0, 0x90, 0xe0, 0x1e, 0x7a, 0xf6, 0xca, 0x93, + 0x44, 0xd4, 0xf1, 0xa8, 0xd2, 0x78, 0xf3, 0x3e, 0x7d, 0xba, 0x5e, 0xfb, 0x06, 0x65, 0x64, 0x3b, + 0x9b, 0x90, 0x93, 0xbf, 0xef, 0xa7, 0xc1, 0x23, 0x7d, 0x33, 0x62, 0xb9, 0x35, 0xb4, 0x6b, 0x9e, + 0x33, 0x8d, 0xfb, 0x07, 0x8d, 0x25, 0x54, 0xa6, 0xde, 0xe2, 0x89, 0xda, 0x54, 0x78, 0xf0, 0x27, + 0xde, 0xc1, 0xc1, 0x19, 0x82, 0x07, 0x67, 0x75, 0xaf, 0x33, 0xeb, 0x90, 0x50, 0xc6, 0xce, 0xea, + 0xfb, 0x7f, 0x03, 0x31, 0xc9, 0xe1, 0x77, 0x03, 0x9e, 0xa4, 0x14, 0xc3, 0x1d, 0x7a, 0x52, 0x51, + 0x1a, 0xcd, 0x92, 0x3c, 0x68, 0xf9, 0x41, 0x4c, 0xf9, 0x5b, 0x17, 0x7b, 0x50, 0x83, 0xc0, 0x8f, + 0x5e, 0x7e, 0x16, 0x77, 0x5b, 0xa8, 0xc1, 0x15, 0x04, 0x0b, 0xdc, 0xef, 0xa1, 0x26, 0xaf, 0xe2, + 0x34, 0x9a, 0x4d, 0xf3, 0x0b, 0xce, 0xff, 0x76, 0x60, 0x51, 0x0d, 0xdb, 0x7f, 0xb3, 0x96, 0x77, + 0x22, 0xf6, 0xbb, 0x42, 0x8d, 0x02, 0x62, 0x29, 0x95, 0x98, 0xac, 0x0b, 0xd2, 0x3b, 0xf0, 0x6a, + 0x9c, 0xc6, 0xb3, 0x24, 0xef, 0xad, 0xfc, 0x24, 0x6e, 0x8c, 0x5f, 0x37, 0x55, 0xd5, 0x5d, 0x56, + 0x4d, 0x42, 0xa3, 0x73, 0xc8, 0x55, 0xf6, 0xd5, 0x7e, 0x7f, 0x54, 0xd3, 0x34, 0x9a, 0xc5, 0xf9, + 0x39, 0xe4, 0xbe, 0x2f, 0xda, 0xab, 0xa4, 0xed, 0xfb, 0xa2, 0x3d, 0xbf, 0xb2, 0x44, 0x0a, 0x81, + 0x29, 0xd1, 0xbe, 0xb2, 0xf7, 0x5c, 0x5d, 0x1a, 0xa7, 0xae, 0xda, 0xea, 0xd2, 0x38, 0xf9, 0x5e, + 0x8c, 0xf0, 0x50, 0x83, 0x53, 0xd7, 0xcc, 0x96, 0x6f, 0xf2, 0xd6, 0x32, 0x2f, 0xc1, 0x22, 0xa9, + 0x9b, 0x9e, 0x07, 0x3b, 0xbf, 0x15, 0xd7, 0xa1, 0x60, 0xe5, 0x1e, 0xd9, 0xdf, 0xff, 0x8d, 0xc4, + 0x70, 0xd9, 0xd4, 0xbf, 0xb8, 0xed, 0xc6, 0x54, 0x10, 0xc2, 0x6d, 0x43, 0x3f, 0x7a, 0x99, 0x8a, + 0x2b, 0x4f, 0x85, 0xa3, 0xd5, 0x66, 0xe3, 0x81, 0x42, 0xf6, 0xa3, 0xfc, 0x14, 0xc9, 0x8f, 0x22, + 0x81, 0xba, 0xec, 0xce, 0xe3, 0x70, 0xfe, 0x0a, 0xf8, 0xda, 0x3f, 0xf2, 0x45, 0x97, 0x37, 0x4b, + 0x0e, 0x57, 0xb7, 0xe3, 0xe8, 0x22, 0xef, 0xad, 0xfc, 0x22, 0xde, 0x76, 0x72, 0xd5, 0x90, 0x6d, + 0xe8, 0x9b, 0xa9, 0x40, 0x8d, 0x43, 0xcd, 0xe5, 0x01, 0x8f, 0xbd, 0x04, 0x8d, 0xae, 0x20, 0x74, + 0x2b, 0x4b, 0x06, 0x6b, 0x1f, 0xa6, 0x91, 0xe4, 0x17, 0x7c, 0x9e, 0x89, 0x77, 0x06, 0xb3, 0xe3, + 0x36, 0x66, 0x5b, 0x67, 0x75, 0xa6, 0xab, 0xf9, 0xed, 0x02, 0x4b, 0x78, 0x62, 0xb4, 0xe6, 0x5d, + 0x5c, 0x47, 0x7f, 0x06, 0xf1, 0xe2, 0xe9, 0xe7, 0xf3, 0x38, 0xac, 0xe6, 0xd7, 0xff, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x61, 0x2f, 0x9f, 0xcd, 0xe6, 0x02, 0x00, 0x00, } diff --git a/flows/codelingo/rewrite/rpc/rewrite.proto b/flows/codelingo/rewrite/rpc/rewrite.proto index 22618494..c779b234 100644 --- a/flows/codelingo/rewrite/rpc/rewrite.proto +++ b/flows/codelingo/rewrite/rpc/rewrite.proto @@ -40,10 +40,7 @@ package rpc; message Request { string host = 1; string hostname = 2; - oneof ownerOrDepot { - string owner = 12; - string depot= 13; - } + bool generateComments = 3; string repo= 4; string sha = 5; repeated string Patches = 6; @@ -52,6 +49,10 @@ message Request { string vcs = 9; string dotlingo = 10; string dir = 11; + oneof ownerOrDepot { + string owner = 12; + string depot= 13; + } } message Hunk { @@ -59,5 +60,8 @@ message Hunk { int32 startOffset =2; int32 endOffset =3; string SRC =4; - string decoratorOptions =5; + string comment =5; + // Hack to pass flag from CLIApp to writer + string commentOutputFile = 6; + string decoratorOptions =7; } diff --git a/flows/codelingo/search/bin/darwin/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/search/bin/darwin/amd64/0.0.0/cmd.tar.gz deleted file mode 100644 index bd792c86..00000000 Binary files a/flows/codelingo/search/bin/darwin/amd64/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/search/bin/linux/386/0.0.0/cmd.tar.gz b/flows/codelingo/search/bin/linux/386/0.0.0/cmd.tar.gz deleted file mode 100644 index e8c07902..00000000 Binary files a/flows/codelingo/search/bin/linux/386/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/search/bin/linux/amd64/0.0.0/cmd.tar.gz b/flows/codelingo/search/bin/linux/amd64/0.0.0/cmd.tar.gz deleted file mode 100644 index e8c07902..00000000 Binary files a/flows/codelingo/search/bin/linux/amd64/0.0.0/cmd.tar.gz and /dev/null differ diff --git a/flows/codelingo/search/bin/windows/amd64/0.0.0/cmd.exe.zip b/flows/codelingo/search/bin/windows/amd64/0.0.0/cmd.exe.zip deleted file mode 100644 index 671ef9bf..00000000 Binary files a/flows/codelingo/search/bin/windows/amd64/0.0.0/cmd.exe.zip and /dev/null differ diff --git a/flows/codelingo/search/main.go b/flows/codelingo/search/main.go index 478b36fe..2b096cb3 100644 --- a/flows/codelingo/search/main.go +++ b/flows/codelingo/search/main.go @@ -6,15 +6,13 @@ import ( "fmt" "io/ioutil" - "github.com/golang/protobuf/ptypes" - - flowutil "github.com/codelingo/codelingo/sdk/flow" "github.com/codelingo/lingo/app/commands/verify" "github.com/codelingo/lingo/app/util" "github.com/codelingo/lingo/service" grpcclient "github.com/codelingo/lingo/service/grpc" "github.com/codelingo/rpc/flow" "github.com/codelingo/rpc/flow/client" + "github.com/golang/protobuf/ptypes" "github.com/juju/errors" "github.com/urfave/cli" ) @@ -32,6 +30,11 @@ var searchCommand = cli.Command{ Value: "json-pretty", Usage: "How to format the found results. Possible values are: json, json-pretty.", }, + cli.BoolFlag{ + Name: "insecure", + Hidden: true, + Usage: "Review without TLS", + }, }, Description: ` ""$ lingo search " . @@ -40,9 +43,9 @@ var searchCommand = cli.Command{ } func main() { - if err := flowutil.Run(searchCommand); err != nil { - flowutil.HandleErr(err) - } + // if err := flowutil.Run(searchCommand); err != nil { + // flowutil.HandleErr(err) + // } } func searchAction(ctx *cli.Context) { @@ -82,7 +85,7 @@ func searchCMD(cliCtx *cli.Context) (string, error) { args := cliCtx.Args() if len(args) == 0 { - return "", errors.New("Please specify the filepath to a rpc.yaml file.") + return "", errors.New("please specify the filepath to a rpc.yaml file.") } dotlingo, err := ioutil.ReadFile(args[0]) @@ -90,7 +93,10 @@ func searchCMD(cliCtx *cli.Context) (string, error) { return "", errors.Trace(err) } - conn, err := service.GrpcConnection(service.LocalClient, service.FlowServer) + insecure := cliCtx.IsSet("insecure") + util.Logger.Debugf("insecure %t", insecure) + + conn, err := service.GrpcConnection(service.LocalClient, service.FlowServer, insecure) if err != nil { return "", errors.Trace(err) } @@ -110,11 +116,17 @@ func searchCMD(cliCtx *cli.Context) (string, error) { return "", errors.Trace(err) } + reqc := make(chan *flow.Request) + go func() { + reqc <- &flow.Request{ + Flow: "search", + Payload: payload, + } + close(reqc) + }() + fmt.Println("Running search flow...") - resultc, errorc, err := c.Run(ctx, &flow.Request{ - Flow: "search", - Payload: payload, - }) + resultc, errorc, err := c.Run(ctx, reqc) if err != nil { return "", errors.Trace(err) } diff --git a/public/img/CLLogo.svg b/public/img/CLLogo.svg new file mode 100644 index 00000000..873f64ab --- /dev/null +++ b/public/img/CLLogo.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/registry/tenets.yaml b/registry/tenets.yaml index 93365eb7..3a24aadf 100644 --- a/registry/tenets.yaml +++ b/registry/tenets.yaml @@ -16,71 +16,76 @@ codelingo: redundant-defer-wraps: hasIssues: false tags: - - golang - - go - - python - - py - cockroachdb: - description: Best Practices for CockroachDB from their contributor docs. - version: 0.0.0 - tenets: - avoid-bool-params: - hasIssues: true - fmt-verbs: - hasIssues: true - func-args-inline-comments: - hasIssues: true - line-length-limit: - hasIssues: true - wrapping-func-signatures: - hasIssues: true - tags: - - golang - - go - - cockroach - - cockroachdb + - golang + - go + - python + - py code-review-comments: description: Best Practices for Golang from Code Review Comments. version: 0.0.0 tenets: + avoid-meaningless-package-names: + hasIssues: false avoid-renaming-imports: - hasIssues: true + hasIssues: false context-first-arg: hasIssues: false + context-in-struct: + hasIssues: false do-not-discard-errors: hasIssues: true go-error-fmt: hasIssues: false use-crypto-rand: hasIssues: true + same-receiver-name: + hasIssues: false + receiver-name-short-reflect-identity: + hasIssues: false + declare-empty-slice: + hasIssues: false + camel-case-constants: + hasIssues: false tags: - - golang - - go + - golang + - go effective-go: description: Best Practices for Golang from Effective Go. version: 0.0.0 tenets: - avoid-annotations-in-comments: - hasIssues: false comment-first-word-as-subject: hasIssues: false - good-package-name: + update-comment-first-word-as-subject: hasIssues: false - package-comment: - hasIssues: true single-method-interface-name: hasIssues: false - underscores-in-name: + unnecessary-else: + hasIssues: false + defer-close-file: + hasIssues: false + reuse-variable-name-type-switch: + hasIssues: false + no-get-in-getters-name: + hasIssues: false + initialize-using-composite-literal: + hasIssues: false + loop-variable-used-in-go-routine: hasIssues: false tags: - - golang - - go + - golang + - go go: description: Best Practices for Golang. version: 0.0.0 tenets: bool-param: hasIssues: false + break-select-in-for: + hasIssues: false + check-reader-bytes-before-err: + hasIssues: false + default-in-type-switch: + hasIssues: false empty-slice: hasIssues: false global-var: @@ -89,25 +94,33 @@ codelingo: hasIssues: false goto: hasIssues: false - marshalling: - hasIssues: false - nil-only-functions: + non-directional-chan: + hasIssues: true + non-exiting-goroutine: hasIssues: true - println-format-strings: - hasIssues: false reallocated-slice: hasIssues: false - sprintf: - hasIssues: true - tested: + reassigned-error: hasIssues: false - todo: + shadowed-variable: + hasIssues: false + surprising-for-direction: + hasIssues: false + ticker-in-for-select: + hasIssues: false + unchecked-type-assertion: hasIssues: false unconvert: hasIssues: false + shared-count-datarace: + hasIssues: false + avoid-delayed-must-calls: + hasIssues: false + insecure-http-connection: + hasIssues: false tags: - - golang - - go + - golang + - go jenkinsx: description: Best practices from the jenkins-x/js repository. version: 0.0.0 @@ -120,28 +133,45 @@ codelingo: hasIssues: false test-package-name: hasIssues: false - tparallel: + parallel-integration-tests: hasIssues: false tags: - - jenkins - - jenkins-x - - golang - - go - kubernetes: - description: Best practices from the kubernetes repository. + - jenkins + - jenkins-x + - golang + - go + juju-juju: + description: Rules finding potential issues specific to juju. version: 0.0.0 tenets: - flags-have-underscores: + unmanaged-goroutine-in-worker: hasIssues: false - jsonapi-kind-compulsory-metadata: + tags: + - juju + - golang + - go + landmines: + description: Best practices from the The Three Go Landmines gist. + version: 0.0.0 + tenets: + shadowed-func-parameter: hasIssues: false looped-vars-outside-loop: hasIssues: false misused-nil-interface: hasIssues: false - new-package-requires-test: + tags: + - golang + - go + kubernetes: + description: Best practices from the kubernetes repository. + version: 0.0.0 + tenets: + flags-have-underscores: hasIssues: false - shadowed-func-parameter: + jsonapi-kind-compulsory-metadata: + hasIssues: false + new-package-requires-test: hasIssues: false well-formed-lists: hasIssues: false @@ -156,9 +186,9 @@ codelingo: well-named-package: hasIssues: false tags: - - kubernetes - - golang - - go + - kubernetes + - golang + - go lightning-network-daemon: description: Best practices from the lightning network daemon repository. version: 0.0.0 @@ -168,19 +198,20 @@ codelingo: exported-package-is-tested: hasIssues: false tags: - - lnd - - golang - - go - modica: - description: Tenets written for Modica + - lnd + - golang + - go + mozillazg-go-cos: + description: Tenets written for mozillazg/go-cos. version: 0.0.0 tenets: - null-check: - hasIssues: false - transport: + always-close-object-response-body: hasIssues: false tags: - - php + - golang + - go + - go-cos + - mozillazg php: description: Best practices for PHP. version: 0.0.0 @@ -199,10 +230,39 @@ codelingo: hasIssues: false phplint: hasIssues: false - sql-concats: + tags: + - php + psr1: + description: Best practices for PHP from PSR-1. + version: 0.0.0 + tenets: + camel-case-method-name: + hasIssues: false + tags: + - php + - psr + - psr1 + psr2: + description: Best practices for PHP from PSR-2. + version: 0.0.0 + tenets: + elseif-not-else-if: + hasIssues: false + lower-case-constant-values: + hasIssues: false + tags: + - php + - psr + - psr2 + drupal: + description: Best practices for PHP from Drupal. + version: 0.0.0 + tenets: + upper-case-constant-values: hasIssues: false tags: - - php + - php + - drupal west: description: Tenets written for West version: 0.0.0 @@ -214,8 +274,29 @@ codelingo: switch: hasIssues: false tags: - - c# - - csharp - - cpp - - c++ - + - c# + - csharp + - cpp + - c++ + rfjakob-gocryptfs: + description: Tenets written for rfjakob-gocryptfs + version: 0.0.0 + tenets: + missing-close-file: + hasIssues: false + tags: + - go + - rfjakob + - gocryptfs + lomik-go-carbon: + description: Tenets written for lomik-go-carbon + version: 0.0.0 + tenets: + assignment-to-nil-map: + hasIssues: false + missing-stop-ticker: + hasIssues: false + tags: + - go + - lomik + - go-carbon diff --git a/sdk/flow/build.sh b/sdk/flow/build.sh index a8fbe951..b493fde8 100755 --- a/sdk/flow/build.sh +++ b/sdk/flow/build.sh @@ -14,8 +14,6 @@ codelingoPath="$path/src/github.com/codelingo/codelingo" flowPath="$codelingoPath/flows/$owner/$name" v=" -windows,386;\ -linux,386;\ windows,amd64;\ linux,amd64;\ darwin,amd64;" diff --git a/sdk/flow/buildall.sh b/sdk/flow/buildall.sh index 0cce5439..7d18e0b6 100755 --- a/sdk/flow/buildall.sh +++ b/sdk/flow/buildall.sh @@ -11,8 +11,6 @@ fi codelingoPath="$path/src/github.com/codelingo/codelingo" v=" -windows,386;\ -linux,386;\ windows,amd64;\ linux,amd64;\ darwin,amd64;" diff --git a/sdk/flow/cli.go b/sdk/flow/cli.go index 659a0502..9632cc51 100644 --- a/sdk/flow/cli.go +++ b/sdk/flow/cli.go @@ -40,7 +40,7 @@ type flowRunner struct { type CLIApp struct { cli.App - Request func(*cli.Context) (chan proto.Message, chan error, func(), error) + Request func(*cli.Context) (chan proto.Message, <-chan *UserVar, chan error, func(), error) // Help data Tagline string @@ -49,12 +49,18 @@ type CLIApp struct { type DecoratorApp struct { cli.App ConfirmDecorated func(*cli.Context, proto.Message) (bool, error) + SetUserVar func(*UserVar) // Help info DecoratorUsage string DecoratorExample string } +// NewFlow creates a flowRunner from a CLIApp. +// For flows that query the flow server it overrides the action with command function, which +// runs the cliApp's Request function and listens on the channels it returns. +// TODO: move away from flowRunner model by explicitly defining the command function as the +// action in the flow. func NewFlow(cliApp *CLIApp, decoratorApp *DecoratorApp) *flowRunner { // setBaseApp overrides Action with the help action. We don't want this @@ -172,6 +178,10 @@ func setBaseApp(cliApp *CLIApp) { Usage: "the Tenet `FILE` to run rewrite over. If the flag is not set, codelingo.yaml files are read from the branch being rewritten.", // Destination: &language, }, + cli.BoolFlag{ + Name: "no-fatal", + Usage: "the command will be run in 'no-fatal' mode, which will allow an Action to keep running even after receiving an error", + }, } app.Compiled = time.Now() app.EnableBashCompletion = true @@ -189,6 +199,10 @@ func setBaseApp(cliApp *CLIApp) { // TODO(waigani) incorrect usage func +// Run runs the CLI app. We assume that cliApp's uses f.command as its action to query +// the flow server and stream back decorated/confirmed results on f.decoratedResultc. +// Special cliApps that don't use the flow server have their own custom actions, in which +// case the chans returned from here will not be closed. func (f *flowRunner) Run() (chan *DecoratedResult, chan error) { go func() { @@ -210,6 +224,12 @@ func (f *flowRunner) action(ctx *cli.Context) { } func (f *flowRunner) command(ctx *cli.Context) (err error) { + isNoFatalMode := ctx.Bool("no-fatal") + if isNoFatalMode { + fmt.Println("WARNING: command running in 'no fatal error' mode") + } + + var errs []error wg := sync.WaitGroup{} wg.Add(1) defer func() { @@ -218,7 +238,7 @@ func (f *flowRunner) command(ctx *cli.Context) (err error) { }() defer wg.Done() - resultc, errc, cancel, err := f.RunCLI() + resultc, userVarC, errc, cancel, err := f.RunCLI() if err != nil { return errors.Trace(err) } @@ -235,7 +255,17 @@ l: } util.Logger.Debugf("Result error: %s", errors.ErrorStack(err)) - return errors.Trace(err) + if !isNoFatalMode { + return errors.Trace(err) + } + errs = append(errs, err) + case v, ok := <-userVarC: + if !ok { + userVarC = nil + break + } + + f.SetUserVar(v) case result, ok := <-resultc: if !ok { resultc = nil @@ -284,11 +314,20 @@ l: cancel() return errors.New("timed out waiting for issue") } - if resultc == nil && errc == nil { + if resultc == nil && errc == nil && userVarC == nil { break l } } + if isNoFatalMode { + fmt.Println("\n\n", strings.Repeat("-", 15)) + fmt.Printf("NO FATAL MODE: The following %d errors were ignored:\n", len(errs)) + for _, err := range errs { + fmt.Println("ERROR:", errors.ErrorStack(err)) + } + fmt.Print(strings.Repeat("-", 15), "\n\n") + } + return } @@ -303,16 +342,15 @@ func (f *flowRunner) CliCtx() (*cli.Context, error) { return f.cliCtx, nil } -// TODO(waicmdgani) move this to codelingo/sdk/flow -func (f *flowRunner) RunCLI() (chan proto.Message, chan error, func(), error) { +func (f *flowRunner) RunCLI() (chan proto.Message, <-chan *UserVar, chan error, func(), error) { ctx, err := f.CliCtx() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } if ctx.Bool("debug") { err := util.SetDebugLogger() if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } } @@ -328,6 +366,39 @@ func (f *flowRunner) ConfirmDecorated(decorator string, payload proto.Message) ( return f.decoratorApp.ConfirmDecorated(ctx, payload) } +func (f *flowRunner) SetUserVar(userVar *UserVar) { + f.decoratorApp.SetUserVar(userVar) +} + +// DecoratorArgs parses a decorator string and returns the CLI arguments +func DecoratorArgs(decStr string) []string { + return strings.Split(decStr, " ")[1:] +} + +// ParseArgs formats args for urfave/cli +// urfave/cli doesn't distinguish between literal and variable args e.g. "this +// arg" vs thisArg. "this arg" gets parsed as two args. The function below +// takes all args as the input. The cli lib pads {} chars which we undo below. +// A better cli lib should be used. +func ParseArgs(args []string) string { + var finalArg string + noSpace := map[string]bool{ + "{": true, + "}": true, + } + + for _, arg := range args { + + finalArg += arg + + if !noSpace[arg] { + finalArg += " " + } + + } + return strings.TrimRight(finalArg, " ") +} + func NewCtx(app *cli.App, input ...string) (*cli.Context, error) { fSet := flag.NewFlagSet(app.Name, flag.ContinueOnError) diff --git a/sdk/flow/cli_test.go b/sdk/flow/cli_test.go index 5a8a0edd..59ded22d 100644 --- a/sdk/flow/cli_test.go +++ b/sdk/flow/cli_test.go @@ -40,8 +40,8 @@ var cliCMD = &CLIApp{ }, }, }, - Request: func(*cli.Context) (chan proto.Message, chan error, func(), error) { - return nil, nil, nil, nil + Request: func(*cli.Context) (chan proto.Message, <-chan *UserVar, chan error, func(), error) { + return nil, nil, nil, nil, nil }, } @@ -65,3 +65,45 @@ var decoratorCMD = &DecoratorApp{ }, }, } + +func (s *flowSuite) TestDecoratorArgs(c *gc.C) { + for _, test := range decoratorArgsTest { + result := DecoratorArgs(test.input) + c.Assert(result, jc.DeepEquals, test.expected) + } +} + +var decoratorArgsTest = []struct { + input string + expected []string +}{ + { + input: "review -f -someflag", + expected: []string{"-f", "-someflag"}, + }, + { + input: "review -f \"{{something}}\"", + expected: []string{"-f", "\"{{something}}\""}, + }, + { + input: `rewrite -r "errors.Errorf(\"{{formatString}})\""`, + expected: []string{"-r", `"errors.Errorf(\"{{formatString}})\""`}, + }, +} + +func (s *flowSuite) TestParseArgs(c *gc.C) { + for _, test := range parseArgsTest { + result := ParseArgs(test.input) + c.Assert(result, jc.DeepEquals, test.expected) + } +} + +var parseArgsTest = []struct { + input []string + expected string +}{ + { + input: []string{"-r", `"errors.Errorf(\"{{formatString}})\""`}, + expected: `-r "errors.Errorf(\"{{formatString}})\""`, + }, +} diff --git a/sdk/flow/confirm.go b/sdk/flow/confirm.go index 2fa321b9..61a66ea5 100644 --- a/sdk/flow/confirm.go +++ b/sdk/flow/confirm.go @@ -7,6 +7,7 @@ package flow import ( "fmt" "log" + "os" "github.com/codelingo/lingo/app/util" "github.com/juju/errors" @@ -51,7 +52,8 @@ func (item *ConfirmerItem) Confirm(ctx *cli.Context) (bool, error) { fmt.Scanln(&option) action, err := item.action(option) if err != nil { - return false, errors.Trace(err) + fmt.Fprintf(os.Stderr, "no action found for option %q", option) + return item.Confirm(ctx) } pass, retry, err := action() diff --git a/sdk/flow/request.go b/sdk/flow/request.go index d21ad856..b8e8e58b 100644 --- a/sdk/flow/request.go +++ b/sdk/flow/request.go @@ -3,46 +3,51 @@ package flow import ( "context" - "github.com/golang/protobuf/ptypes" - - "github.com/golang/protobuf/proto" - "github.com/codelingo/lingo/app/util" "github.com/codelingo/lingo/service" grpcclient "github.com/codelingo/lingo/service/grpc" grpcflow "github.com/codelingo/rpc/flow" "github.com/codelingo/rpc/flow/client" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" "github.com/juju/errors" ) -func RunFlow(flowName string, req proto.Message, newItem func() proto.Message) (chan proto.Message, chan error, func(), error) { - +// RunFlow calls a given flow on the flow server and marshalls replies as a given type +func RunFlow(flowName string, req proto.Message, newItem func() proto.Message, setDefaults func(proto.Message) proto.Message) (chan proto.Message, <-chan *UserVar, chan error, func(), error) { ctx, cancel := util.UserCancelContext(context.Background()) payload, err := ptypes.MarshalAny(req) if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } - rpcReq := &grpcflow.Request{ - Flow: flowName, - Payload: payload, - } + rpcReqC := make(chan *grpcflow.Request) + go func() { + rpcReqC <- &grpcflow.Request{ + Flow: flowName, + Payload: payload, + } + }() - replyc, runErrc, err := Request(ctx, rpcReq) + allReplyc, runErrc, err := Request(ctx, rpcReqC) if err != nil { - return nil, nil, nil, errors.Trace(err) + return nil, nil, nil, nil, errors.Trace(err) } - itemc, marshalErrc := MarshalChan(replyc, newItem) - return itemc, ErrFanIn(runErrc, marshalErrc), cancel, nil + replyc, userVarC, setterErrc := fanOutUserVars(allReplyc, rpcReqC) + itemc, marshalErrc := MarshalChan(replyc, newItem, setDefaults) + return itemc, userVarC, ErrFanIn(ErrFanIn(runErrc, marshalErrc), setterErrc), cancel, nil } -func Request(ctx context.Context, req *grpcflow.Request) (chan *grpcflow.Reply, chan error, error) { +func Request(ctx context.Context, reqC <-chan *grpcflow.Request) (chan *grpcflow.Reply, chan error, error) { + util.Logger.Debug("opening connection to flow server ...") conn, err := service.GrpcConnection(service.LocalClient, service.FlowServer, true) if err != nil { return nil, nil, errors.Trace(err) } + util.Logger.Debug("...connection to flow server opened") + c := client.NewFlowClient(conn) // Create context with metadata @@ -51,11 +56,11 @@ func Request(ctx context.Context, req *grpcflow.Request) (chan *grpcflow.Reply, return nil, nil, errors.Trace(err) } - replyc, runErrc, err := c.Run(ctx, req) + replyc, runErrc, err := c.Run(ctx, reqC) return replyc, runErrc, errors.Trace(err) } -func MarshalChan(replyc chan *grpcflow.Reply, newItem func() proto.Message) (chan proto.Message, chan error) { +func MarshalChan(replyc chan *grpcflow.Reply, newItem func() proto.Message, setDefaults func(proto.Message) proto.Message) (chan proto.Message, chan error) { itemc := make(chan proto.Message) errc := make(chan error) @@ -78,7 +83,7 @@ func MarshalChan(replyc chan *grpcflow.Reply, newItem func() proto.Message) (cha continue } - itemc <- item + itemc <- setDefaults(item) } close(errc) close(itemc) diff --git a/sdk/flow/user_variable.go b/sdk/flow/user_variable.go new file mode 100644 index 00000000..f35f2b0a --- /dev/null +++ b/sdk/flow/user_variable.go @@ -0,0 +1,94 @@ +package flow + +import ( + "bufio" + "fmt" + "os" + + grpcflow "github.com/codelingo/rpc/flow" + "github.com/golang/protobuf/ptypes" + "github.com/juju/errors" +) + +// A UserVar allows users and other external agents to set variable values while a query +// is being executed. +// TODO: UserVar code is copied from the Platform +type UserVar struct { + VarC chan<- string + Name string + DefaultValue string +} + +// Set sets a variable from user input +func (s *UserVar) Set() { + fmt.Printf("%s [%s]: ", s.Name, s.DefaultValue) + scanner := bufio.NewScanner(os.Stdin) + if scanner.Scan() { + line := scanner.Text() + if line != "" { + s.set(line) + return + } + } + s.SetAsDefault() +} + +// SetAsDefault sets the variable to its default value +func (s *UserVar) SetAsDefault() { + s.set(s.DefaultValue) +} + +// Set sets the value of the variable +func (s *UserVar) set(val string) { + s.VarC <- val + close(s.VarC) +} + +// fanOutUserVars puts user variable setters on their own channel +func fanOutUserVars(incoming <-chan *grpcflow.Reply, flowsetterc chan<- *grpcflow.Request) (chan *grpcflow.Reply, <-chan *UserVar, chan error) { + outgoingc := make(chan *grpcflow.Reply) + userVarc := make(chan *UserVar) + errc := make(chan error) + + go func() { + defer func() { + close(outgoingc) + close(userVarc) + close(errc) + close(flowsetterc) + }() + + for msg := range incoming { + setRequest := &grpcflow.UserVariableSetter{} + err := ptypes.UnmarshalAny(msg.Payload, setRequest) + if err == nil { + varC := make(chan string) + userVar := &UserVar{ + VarC: varC, + Name: setRequest.Name, + DefaultValue: setRequest.Default, + } + + userVarc <- userVar + + // Currently immediately sets the variable to its default value + // TODO: pass setter along the chan + inner, err := ptypes.MarshalAny(&grpcflow.UserVariableValue{ + Value: <-varC, + Id: setRequest.Id, + }) + if err != nil { + errc <- errors.Trace(err) + } + + flowsetterc <- &grpcflow.Request{ + Payload: inner, + } + } else { + outgoingc <- msg + } + } + }() + + return outgoingc, userVarc, errc +} diff --git a/sdk/flow/util.go b/sdk/flow/util.go index e207ea18..99ea4017 100644 --- a/sdk/flow/util.go +++ b/sdk/flow/util.go @@ -26,6 +26,8 @@ func ReadDotLingo(ctx *cli.Context) (string, error) { return string(dotlingo), nil } +// Repeated code in platform +// TODO: variadic fan in - read only func ErrFanIn(err1c, err2c chan error) chan error { errc := make(chan error) go func() { diff --git a/tags b/tags index 33ed3158..7170d044 100644 --- a/tags +++ b/tags @@ -4,13 +4,17 @@ !_TAG_PROGRAM_NAME Exuberant Ctags // !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ !_TAG_PROGRAM_VERSION 5.9~svn20110310 // -AddFlags tenets/codelingo/k8/flags-have-underscores/example.go /^func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {$/;" f -AdmissionRequest tenets/codelingo/k8/jsonapi-kind-compulsory-metadata/example.go /^type AdmissionRequest struct {$/;" t +AddFlags tenets/codelingo/kubernetes/flags-have-underscores/example.go /^func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {$/;" f +AdmissionRequest tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/example.go /^type AdmissionRequest struct {$/;" t Alpha tenets/codelingo/go/golint/const-block.go /^ Alpha = "a"$/;" c -AnotherKind tenets/codelingo/k8/well-named-lists/example.go /^type AnotherKind struct {$/;" t -AnotherKindOfList tenets/codelingo/k8/well-formed-lists/example.go /^type AnotherKindOfList struct {$/;" t +AnotherKind tenets/codelingo/kubernetes/well-named-lists/example.go /^type AnotherKind struct {$/;" t +AnotherKindOfList tenets/codelingo/kubernetes/well-formed-lists/example.go /^type AnotherKindOfList struct {$/;" t AutoLink flows/codelingo/docs/render/terminal.go /^func (options *Console) AutoLink(out *bytes.Buffer, link []byte, kind int) {$/;" f +Bad_Interface tenets/codelingo/effective-go/underscores-in-name/example.go /^type Bad_Interface interface {$/;" t +Bar tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func Bar() {}$/;" f +Baz tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func Baz() {}$/;" f Beta tenets/codelingo/go/golint/const-block.go /^ Beta = "b"$/;" c +BetterInterface tenets/codelingo/effective-go/underscores-in-name/example.go /^type BetterInterface interface {$/;" t BlockCode flows/codelingo/docs/render/terminal.go /^func (options *Console) BlockCode(out *bytes.Buffer, text []byte, lang string) {$/;" f BlockHtml flows/codelingo/docs/render/terminal.go /^func (options *Console) BlockHtml(out *bytes.Buffer, text []byte) {$/;" f BlockQuote flows/codelingo/docs/render/terminal.go /^func (options *Console) BlockQuote(out *bytes.Buffer, text []byte) {$/;" f @@ -20,46 +24,48 @@ CLIApp flows/codelingo/review/review/cli.go /^var CLIApp = &flowutil.CLIApp{$/;" CLIApp flows/codelingo/rewrite/rewrite/cli.go /^var CLIApp = &flowutil.CLIApp{$/;" v CLIApp sdk/flow/cli.go /^type CLIApp struct {$/;" t CPP_CONST tenets/codelingo/go/golint/names.go /^ CPP_CONST = 1 \/\/ MATCH \/ALL_CAPS.*CamelCase\/$/;" c -Cat tenets/codelingo/k8/misused-nil-interface/example.go /^type Cat interface {$/;" t +Cat tenets/codelingo/landmines/misused-nil-interface/example.go /^type Cat interface {$/;" t CliCtx sdk/flow/cli.go /^func (f *flowRunner) CliCtx() (*cli.Context, error) {$/;" f ClientData tenets/codelingo/go/marshalling/example.go /^type ClientData struct {$/;" t CodeSpan flows/codelingo/docs/render/terminal.go /^func (options *Console) CodeSpan(out *bytes.Buffer, text []byte) {$/;" f -Combine tenets/west/default/shadowing/project.cpp /^bool Combine(Object* object, Something* something) {$/;" f -ConfigFlags tenets/codelingo/k8/flags-have-underscores/example.go /^type ConfigFlags struct {$/;" t +Combine tenets/codelingo/west/shadowing/project.cpp /^bool Combine(Object* object, Something* something) {$/;" f +ConfigFlags tenets/codelingo/kubernetes/flags-have-underscores/example.go /^type ConfigFlags struct {$/;" t Confirm sdk/flow/confirm.go /^func (item *ConfirmerItem) Confirm(ctx *cli.Context) (bool, error) {$/;" f ConfirmDecorated sdk/flow/cli.go /^func (f *flowRunner) ConfirmDecorated(decorator string, payload proto.Message) (bool, error) {$/;" f ConfirmerItem sdk/flow/confirm.go /^type ConfirmerItem struct {$/;" t Console flows/codelingo/docs/render/terminal.go /^type Console struct {$/;" t Controller tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^class Controller$/;" c -D tenets/west/default/allocation/variable_allocation.cpp /^void D ()$/;" f +D tenets/codelingo/west/allocation/variable_allocation.cpp /^void D ()$/;" f DECAPPHELPTMP sdk/flow/help.go /^var DECAPPHELPTMP = `$/;" v Data util/mdgen/dataStruct/tenets.go /^type Data struct {$/;" t -Dead tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Dead() <-chan struct{} {$/;" f -Dead tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Dead() <-chan struct{} {$/;" f +Dead tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Dead() <-chan struct{} {$/;" f +Dead tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Dead() <-chan struct{} {$/;" f DecoratedResult sdk/flow/cli.go /^type DecoratedResult struct {$/;" t DecoratorApp flows/codelingo/review/review/decorator.go /^var DecoratorApp = &flowutil.DecoratorApp{$/;" v DecoratorApp flows/codelingo/rewrite/rewrite/decorator.go /^var DecoratorApp = &flowutil.DecoratorApp{$/;" v DecoratorApp sdk/flow/cli.go /^type DecoratorApp struct {$/;" t -DeletedArray tenets/west/default/allocation/variable_allocation.cpp /^void DeletedArray(){$/;" f +DecoratorArgs sdk/flow/cli.go /^func DecoratorArgs(decStr string) []string {$/;" f +DeletedArray tenets/codelingo/west/allocation/variable_allocation.cpp /^void DeletedArray(){$/;" f Descriptor flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (*Hunk) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }$/;" f Descriptor flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (*Request) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }$/;" f +Descriptor tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (*HelloReply) Descriptor() ([]byte, []int) {$/;" f +Descriptor tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (*HelloRequest) Descriptor() ([]byte, []int) {$/;" f DoAuthStuff tenets/codelingo/jenkinsx/tparallel/auth.go /^func DoAuthStuff() {}$/;" f -DoTheThing tenets/codelingo/k8/shadowed-func-parameter/example.go /^func DoTheThing(reallyDoIt bool) (err error) {$/;" f DocumentFooter flows/codelingo/docs/render/terminal.go /^func (options *Console) DocumentFooter(out *bytes.Buffer) {$/;" f DocumentHeader flows/codelingo/docs/render/terminal.go /^func (options *Console) DocumentHeader(out *bytes.Buffer) {$/;" f +DoesntCount tenets/codelingo/effective-go/single-method-interface-name/example.go /^type DoesntCount interface {$/;" t Donut tenets/codelingo/go/golint/stutter.go /^type Donut struct{} \/\/ ok because it is the whole name$/;" t DonutMaker tenets/codelingo/go/golint/stutter.go /^type DonutMaker struct{} \/\/ MATCH \/donut\\.DonutMaker.*stutter\/$/;" t DonutMass tenets/codelingo/go/golint/stutter.go /^func (d *Donut) DonutMass() (grams int) { \/\/ okay because it is a method$/;" f DonutRank tenets/codelingo/go/golint/stutter.go /^func DonutRank(d Donut) int { \/\/ MATCH \/donut\\.DonutRank.*stutter\/$/;" f Donuts tenets/codelingo/go/golint/stutter.go /^type Donuts []Donut \/\/ ok because it didn't start a new word$/;" t DoubleEmphasis flows/codelingo/docs/render/terminal.go /^func (options *Console) DoubleEmphasis(out *bytes.Buffer, text []byte) {$/;" f -E tenets/west/default/allocation/variable_allocation.cpp /^void E ()$/;" f +E tenets/codelingo/west/allocation/variable_allocation.cpp /^void E ()$/;" f E2 tenets/codelingo/go/golint/errors.go /^ E2 = fmt.Errorf("blah %d", 5) \/\/ MATCH \/error var.*E2.*ErrFoo\/$/;" v Emphasis flows/codelingo/docs/render/terminal.go /^func (options *Console) Emphasis(out *bytes.Buffer, text []byte) {$/;" f Entity flows/codelingo/docs/render/terminal.go /^func (options *Console) Entity(out *bytes.Buffer, entity []byte) {$/;" f -Err tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Err() error {$/;" f -Err tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Err() error {$/;" f -ErrDidNotWork tenets/codelingo/k8/shadowed-func-parameter/example.go /^var ErrDidNotWork = errors.New("did not work")$/;" v +Err tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Err() error {$/;" f +Err tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Err() error {$/;" f ErrFanIn sdk/flow/util.go /^func ErrFanIn(err1c, err2c chan error) chan error {$/;" f Error tenets/codelingo/go/golint/common-methods.go /^func (T) Error() string { return "" }$/;" f Example tenets/codelingo/php/phplint/BadExample.php /^class Example$/;" c @@ -75,17 +81,19 @@ ExpT tenets/codelingo/go/golint/unexp-return.go /^func ExpT() T { \/\/ ok$/;" f Exported tenets/codelingo/go/golint/unexp-return.go /^func Exported() hidden { \/\/ MATCH \/Exported.*unexported.*hidden\/$/;" f ExportedIntReturner tenets/codelingo/go/golint/unexp-return.go /^func ExportedIntReturner() int { \/\/ MATCH \/ExportedIntReturner.*unexported.*int\/$/;" f F tenets/codelingo/go/golint/4.go /^func (T) F() {} \/\/ MATCH \/exported method T\\.F.*should.*comment.*or.*unexport\/$/;" f -FalseController tenets/modica/default/transport/wrong_controller_name/something.php /^class FalseController extends Controller {$/;" c -Foo tenets/cacophony/default/redundant-defer-wraps/example.go /^type Foo struct {$/;" t +FalseController tenets/codelingo/modica/transport/wrong_controller_name/something.php /^class FalseController extends Controller {$/;" c +Foo tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^type Foo struct {$/;" t +Foo tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func Foo() {}$/;" f Foo tenets/codelingo/go/golint/broken.go /^func () Foo() {}$/;" f FootnoteItem flows/codelingo/docs/render/terminal.go /^func (options *Console) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {}$/;" f FootnoteRef flows/codelingo/docs/render/terminal.go /^func (options *Console) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {$/;" f Footnotes flows/codelingo/docs/render/terminal.go /^func (options *Console) Footnotes(out *bytes.Buffer, text func() bool) {}$/;" f -FreedObject tenets/west/default/allocation/variable_allocation.cpp /^void FreedObject() {$/;" f -Func tenets/west/default/allocation/variable_allocation.cpp /^ObjectOnHeap Func ()$/;" f +FreedObject tenets/codelingo/west/allocation/variable_allocation.cpp /^void FreedObject() {$/;" f +Func tenets/codelingo/west/allocation/variable_allocation.cpp /^ObjectOnHeap Func ()$/;" f G tenets/codelingo/go/golint/4.go /^func (U) G() {}$/;" f Gamma tenets/codelingo/go/golint/const-block.go /^ Gamma = "g"$/;" c -GetACat tenets/codelingo/k8/misused-nil-interface/example.go /^func GetACat() Cat {$/;" f +GetComment flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) GetComment() string {$/;" f +GetCommentOutputFile flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) GetCommentOutputFile() string {$/;" f GetDecoratorOptions flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) GetDecoratorOptions() string {$/;" f GetDepot flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetDepot() string {$/;" f GetDir flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetDir() string {$/;" f @@ -93,10 +101,13 @@ GetDotlingo flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetDot GetEndOffset flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) GetEndOffset() int32 {$/;" f GetFilename flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) GetFilename() string {$/;" f GetFlags flows/codelingo/docs/render/terminal.go /^func (options *Console) GetFlags() int {$/;" f +GetGenerateComments flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetGenerateComments() bool {$/;" f GetHost flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetHost() string {$/;" f GetHostname flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetHostname() string {$/;" f GetIsPullRequest flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetIsPullRequest() bool {$/;" f GetLingoFiles flows/codelingo/docs/docs/docs.go /^func GetLingoFiles(workingDir string) (map[string][]byte, error) {$/;" f +GetMessage tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) GetMessage() string {$/;" f +GetName tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) GetName() string {$/;" f GetOwner flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetOwner() string {$/;" f GetOwnerOrDepot flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetOwnerOrDepot() isRequest_OwnerOrDepot {$/;" f GetPatches flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetPatches() []string {$/;" f @@ -107,20 +118,25 @@ GetSha flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetSha() st GetStartOffset flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) GetStartOffset() int32 {$/;" f GetVcs flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) GetVcs() string {$/;" f GitCMD sdk/flow/git.go /^func GitCMD(args ...string) (out string, err error) {$/;" f +GreeterClient tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^type GreeterClient interface {$/;" t +GreeterServer tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^type GreeterServer interface {$/;" t H tenets/codelingo/go/golint/4.go /^func (*V) H() {} \/\/ MATCH \/exported method V\\.H.*should.*comment.*or.*unexport\/$/;" f H tenets/codelingo/go/golint/5_test.go /^type H int$/;" t HRule flows/codelingo/docs/render/terminal.go /^func (options *Console) HRule(out *bytes.Buffer) {$/;" f HTML tenets/codelingo/go/golint/names.go /^ HTML = 3 \/\/ okay; no underscore$/;" c HandleErr sdk/flow/util.go /^func HandleErr(err error) {$/;" f -Hash tenets/west/default/shadowing/project.cpp /^int Hash(Object* object) {$/;" f +Hash tenets/codelingo/west/shadowing/project.cpp /^int Hash(Object* object) {$/;" f Header flows/codelingo/docs/render/terminal.go /^func (options *Console) Header(out *bytes.Buffer, text func() bool, level int, id string) {$/;" f Hello tenets/codelingo/jenkinsx/parallel-in-tests/hello.go /^func Hello(str string) {$/;" f Hello tenets/codelingo/jenkinsx/test-package-name/hello.go /^func Hello(str string) {$/;" f -HubSource tenets/codelingo/go/reallocated_slice/broken.go /^type HubSource interface {$/;" t -HubSource tenets/codelingo/go/reallocated_slice/fixed.go /^type HubSource interface {$/;" t +Hello tenets/codelingo/psr1/camel-case-method-name/example.php /^class Hello {$/;" c +HelloReply tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^type HelloReply struct {$/;" t +HelloRequest tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^type HelloRequest struct {$/;" t +HubSource tenets/codelingo/go/reallocated-slice/broken.go /^type HubSource interface {$/;" t +HubSource tenets/codelingo/go/reallocated-slice/fixed.go /^type HubSource interface {$/;" t HubTenets util/mdgen/dataStruct/tenets.go /^type HubTenets struct {$/;" t -HubWatcher tenets/codelingo/go/reallocated_slice/broken.go /^type HubWatcher struct {$/;" t -HubWatcher tenets/codelingo/go/reallocated_slice/fixed.go /^type HubWatcher struct {$/;" t +HubWatcher tenets/codelingo/go/reallocated-slice/broken.go /^type HubWatcher struct {$/;" t +HubWatcher tenets/codelingo/go/reallocated-slice/fixed.go /^type HubWatcher struct {$/;" t Hunk flows/codelingo/rewrite/rpc/rewrite.pb.go /^type Hunk struct {$/;" t INFOTMP sdk/flow/help.go /^var INFOTMP = `$/;" v Image flows/codelingo/docs/render/terminal.go /^func (options *Console) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {$/;" f @@ -133,12 +149,14 @@ IsPrepend flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) IsP IsReplace flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) IsReplace() bool {$/;" f IsStartOffset flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) IsStartOffset() bool {$/;" f IsStartToEndOffset flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) IsStartToEndOffset() bool {$/;" f -Kill tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Kill() {$/;" f -Kill tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Kill() {$/;" f -KindOfList tenets/codelingo/k8/well-formed-lists/example.go /^type KindOfList struct {$/;" t -KindOfList tenets/codelingo/k8/well-named-lists/example.go /^type KindOfList struct {$/;" t -KindWithoutName tenets/codelingo/k8/jsonapi-kind-compulsory-metadata/example.go /^type KindWithoutName struct {$/;" t -KindWithoutNamespace tenets/codelingo/k8/jsonapi-kind-compulsory-metadata/example.go /^type KindWithoutNamespace struct {$/;" t +IsTypeOfParams tenets/codelingo/code-review-comments/context-first-arg/example.go /^type IsTypeOfParams struct {$/;" t +Key tenets/codelingo/code-review-comments/use-crypto-rand/example.go /^func Key() string {$/;" f +Kill tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Kill() {$/;" f +Kill tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Kill() {$/;" f +KindOfList tenets/codelingo/kubernetes/well-formed-lists/example.go /^type KindOfList struct {$/;" t +KindOfList tenets/codelingo/kubernetes/well-named-lists/example.go /^type KindOfList struct {$/;" t +KindWithoutName tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/example.go /^type KindWithoutName struct {$/;" t +KindWithoutNamespace tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/example.go /^type KindWithoutNamespace struct {$/;" t LastInsertId tenets/codelingo/go/golint/names.go /^func (t) LastInsertId() (int64, error) { return 0, nil } \/\/ okay because it matches a known style violation$/;" f Len flows/codelingo/rewrite/rewrite/writer.go /^func (o byOffset) Len() int {$/;" f Len tenets/codelingo/go/golint/sort.go /^func (t T) Len() int { return len(t) } \/\/ MATCH \/exported method T\\.Len.*should.*comment\/$/;" f @@ -152,29 +170,30 @@ Link flows/codelingo/docs/render/terminal.go /^func (options *Console) Link(out List flows/codelingo/docs/render/terminal.go /^func (options *Console) List(out *bytes.Buffer, text func() bool, flags int) {$/;" f ListItem flows/codelingo/docs/render/terminal.go /^func (options *Console) ListItem(out *bytes.Buffer, text []byte, flags int) {$/;" f Location tenets/codelingo/go/golint/4.go /^var Location, _ = time.LoadLocation("Europe\/Istanbul") \/\/ not Constantinople$/;" v -MadeUpController tenets/modica/default/transport/made_up/something.php /^class MadeUpController extends Controller {$/;" c +MadeUpController tenets/codelingo/modica/transport/made_up/something.php /^class MadeUpController extends Controller {$/;" c Main tenets/codelingo/php/sql-concats/example.php /^function Main() {$/;" f MakeInvalidJSONRequest tenets/codelingo/go/marshalling/example.go /^func MakeInvalidJSONRequest(s *Server) ([]byte, error) {$/;" f MakeReport flows/codelingo/review/review/review.go /^func MakeReport(cliCtx *cli.Context, issues []*ReportStrt) (string, error) {$/;" f MakeValidJSONRequest tenets/codelingo/go/marshalling/example.go /^func MakeValidJSONRequest(s *Server) ([]byte, error) {$/;" f -MarshalChan sdk/flow/request.go /^func MarshalChan(replyc chan *grpcflow.Reply, newItem func() proto.Message) (chan proto.Message, chan error) {$/;" f -Meow tenets/codelingo/k8/misused-nil-interface/example.go /^func (*Tabby) Meow() { fmt.Println("meow") }$/;" f +MarshalChan sdk/flow/request.go /^func MarshalChan(replyc chan *grpcflow.Reply, newItem func() proto.Message, setDefaults func(proto.Message) proto.Message) (chan proto.Message, chan error) {$/;" f +Meow tenets/codelingo/landmines/misused-nil-interface/example.go /^func (*Tabby) Meow() {$/;" f MethodOnT tenets/codelingo/go/golint/unexp-return.go /^func (T) MethodOnT() hidden { \/\/ MATCH \/method MethodOnT.*unexported.*hidden\/$/;" f -MockAddresses tenets/codelingo/k8/well-named-lock/example.go /^type MockAddresses struct {$/;" t -MockAddresses tenets/codelingo/k8/well-named-locks/example.go /^type MockAddresses struct {$/;" t +MockAddresses tenets/codelingo/kubernetes/well-named-lock/example.go /^type MockAddresses struct {$/;" t +MockAddresses tenets/codelingo/kubernetes/well-named-locks/example.go /^type MockAddresses struct {$/;" t New flows/codelingo/rewrite/rewrite/option/option.go /^func New(ctx *cli.Context) (option, error) {$/;" f NewCtx sdk/flow/cli.go /^func NewCtx(app *cli.App, input ...string) (*cli.Context, error) {$/;" f NewFlow sdk/flow/cli.go /^func NewFlow(cliApp *CLIApp, decoratorApp *DecoratorApp) *flowRunner {$/;" f -NewHubWatcher tenets/codelingo/go/reallocated_slice/broken.go /^func NewHubWatcher(hub HubSource, logger Logger) *HubWatcher {$/;" f -NewHubWatcher tenets/codelingo/go/reallocated_slice/fixed.go /^func NewHubWatcher(hub HubSource, logger Logger) *HubWatcher {$/;" f +NewGreeterClient tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {$/;" f +NewHubWatcher tenets/codelingo/go/reallocated-slice/broken.go /^func NewHubWatcher(hub HubSource, logger Logger) *HubWatcher {$/;" f +NewHubWatcher tenets/codelingo/go/reallocated-slice/fixed.go /^func NewHubWatcher(hub HubSource, logger Logger) *HubWatcher {$/;" f NewRange flows/codelingo/review/review/review.go /^func NewRange(filename string, startLine, endLine int) *flow.IssueRange {$/;" f NoCommitErr sdk/flow/util.go /^func NoCommitErr(err error) bool {$/;" f NoCommitErrMsg sdk/flow/util.go /^const NoCommitErrMsg = "This looks like a new repository. Please make an initial commit before running `lingo review`. This is only Required for the initial commit, subsequent changes to your repo will be picked up by lingo without committing."$/;" c NormalText flows/codelingo/docs/render/terminal.go /^func (options *Console) NormalText(out *bytes.Buffer, text []byte) {$/;" f ORDERED flows/codelingo/docs/render/terminal.go /^ ORDERED$/;" c -Obj tenets/west/default/allocation/variable_allocation.cpp /^Obj::Obj() {$/;" f class:Obj -Obj tenets/west/default/allocation/variable_allocation.cpp /^class Obj {$/;" c file: -Object tenets/west/default/shadowing/project.cpp /^class Object {$/;" c file: +Obj tenets/codelingo/west/allocation/variable_allocation.cpp /^Obj::Obj() {$/;" f class:Obj +Obj tenets/codelingo/west/allocation/variable_allocation.cpp /^class Obj {$/;" c file: +Object tenets/codelingo/west/shadowing/project.cpp /^class Object {$/;" c file: OpenFileConfirmAction sdk/flow/confirm.go /^func OpenFileConfirmAction(filename string, line int64) (bool, bool, error) {$/;" f Options flows/codelingo/review/review/review.go /^type Options struct {$/;" t Other tenets/codelingo/go/golint/sort.go /^func (u U) Other() {} \/\/ MATCH \/exported method U\\.Other.*should.*comment\/$/;" f @@ -182,48 +201,65 @@ OutputResults flows/codelingo/search/main.go /^func OutputResults(results []*flo PROpts flows/codelingo/review/review/reviewpr.go /^type PROpts struct {$/;" t Paragraph flows/codelingo/docs/render/terminal.go /^func (options *Console) Paragraph(out *bytes.Buffer, text func() bool) {$/;" f Parse flows/codelingo/docs/docs/parse/parse.go /^func Parse(dl string) ([]*dotlingo.Dotlingo, error) {$/;" f +ParseArgs sdk/flow/cli.go /^func ParseArgs(args []string) string {$/;" f ParsePR flows/codelingo/review/review/reviewpr.go /^func ParsePR(urlStr string) (*PROpts, error) {$/;" f PkgName tenets/codelingo/go/golint/pkg-caps.go /^package PkgName \/\/ MATCH \/don't use MixedCaps in package name\/$/;" p -Print tenets/cacophony/default/redundant-defer-wraps/example.go /^func (f *Foo) Print() {$/;" f +Print tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^func (f *Foo) Print() {$/;" f Print tenets/codelingo/go/tested/main.go /^func Print() {}$/;" f -ProperlyDeleted tenets/west/default/allocation/variable_allocation.cpp /^void ProperlyDeleted() {$/;" f +ProperlyDeleted tenets/codelingo/west/allocation/variable_allocation.cpp /^void ProperlyDeleted() {$/;" f ProtoMessage flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (*Hunk) ProtoMessage() {}$/;" f ProtoMessage flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (*Request) ProtoMessage() {}$/;" f +ProtoMessage tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (*HelloReply) ProtoMessage() {}$/;" f +ProtoMessage tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (*HelloRequest) ProtoMessage() {}$/;" f Q tenets/codelingo/go/golint/var-decl.go /^type Q bool$/;" t +Qux tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func Qux() {}$/;" f RawHtmlTag flows/codelingo/docs/render/terminal.go /^func (options *Console) RawHtmlTag(out *bytes.Buffer, tag []byte) {$/;" f +Read tenets/codelingo/effective-go/single-method-interface-name/example.go /^type Read interface {$/;" t Read tenets/codelingo/go/golint/common-methods.go /^func (T) Read(p []byte) (n int, err error) { return 0, nil }$/;" f ReadDotLingo flows/codelingo/review/review/review.go /^func ReadDotLingo(ctx *cli.Context) (string, error) {$/;" f ReadDotLingo sdk/flow/util.go /^func ReadDotLingo(ctx *cli.Context) (string, error) {$/;" f +Reader tenets/codelingo/effective-go/single-method-interface-name/example.go /^type Reader interface {$/;" t +RegisterGreeterServer tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {$/;" f ReportStrt flows/codelingo/review/review/review.go /^type ReportStrt struct {$/;" t Request flows/codelingo/rewrite/rpc/rewrite.pb.go /^type Request struct {$/;" t -Request sdk/flow/request.go /^func Request(ctx context.Context, req *grpcflow.Request) (chan *grpcflow.Reply, chan error, error) {$/;" f +Request sdk/flow/request.go /^func Request(ctx context.Context, reqC <-chan *grpcflow.Request) (chan *grpcflow.Reply, chan error, error) {$/;" f Request tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^class Request$/;" c -RequestReview flows/codelingo/review/review/review.go /^func RequestReview(ctx context.Context, req *flow.ReviewRequest, insecure bool) (chan proto.Message, chan error, error) {$/;" f +RequestReview flows/codelingo/review/review/review.go /^func RequestReview(ctx context.Context, req *flow.ReviewRequest, insecure bool) (chan proto.Message, <-chan *flowutil.UserVar, chan error, error) {$/;" f Request_Depot flows/codelingo/rewrite/rpc/rewrite.pb.go /^type Request_Depot struct {$/;" t Request_Owner flows/codelingo/rewrite/rpc/rewrite.pb.go /^type Request_Owner struct {$/;" t Reset flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) Reset() { *m = Hunk{} }$/;" f Reset flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) Reset() { *m = Request{} }$/;" f +Reset tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) Reset() { *m = HelloReply{} }$/;" f +Reset tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) Reset() { *m = HelloRequest{} }$/;" f Run sdk/flow/cli.go /^func (f *flowRunner) Run() (chan *DecoratedResult, chan error) {$/;" f -RunCLI sdk/flow/cli.go /^func (f *flowRunner) RunCLI() (chan proto.Message, chan error, func(), error) {$/;" f -RunFlow sdk/flow/request.go /^func RunFlow(flowName string, req proto.Message, newItem func() proto.Message) (chan proto.Message, chan error, func(), error) {$/;" f +RunCLI sdk/flow/cli.go /^func (f *flowRunner) RunCLI() (chan proto.Message, <-chan *UserVar, chan error, func(), error) {$/;" f +RunFlow sdk/flow/request.go /^func RunFlow(flowName string, req proto.Message, newItem func() proto.Message, setDefaults func(proto.Message) proto.Message) (chan proto.Message, <-chan *UserVar, chan error, func(), error) {$/;" f S tenets/codelingo/go/init/example.go /^type S struct{}$/;" t SayA tenets/codelingo/go/tested/example.go /^func (a *aStruct) SayA() string {$/;" f SayB tenets/codelingo/go/tested/example.go /^func (b *bStruct) SayB() string {$/;" f +SayHello tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {$/;" f SecondPrint tenets/codelingo/go/tested/main.go /^func SecondPrint() {}$/;" f SendData tenets/codelingo/go/marshalling/example.go /^func SendData() error {$/;" f ServeHTTP tenets/codelingo/go/golint/common-methods.go /^func (T) ServeHTTP(w http.ResponseWriter, r *http.Request) {}$/;" f Server tenets/codelingo/go/golint/var-decl.go /^type Server interface{}$/;" t Server tenets/codelingo/go/marshalling/example.go /^type Server struct {$/;" t ServerData tenets/codelingo/go/marshalling/example.go /^type ServerData struct {$/;" t -Set tenets/cacophony/default/redundant-defer-wraps/example.go /^func (f *Foo) Set(bar int) {$/;" f +Set sdk/flow/user_variable.go /^func (s *UserVar) Set() {$/;" f +Set tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^func (f *Foo) Set(bar int) {$/;" f +SetAsDefault sdk/flow/user_variable.go /^func (s *UserVar) SetAsDefault() {$/;" f +SetUserVar sdk/flow/cli.go /^func (f *flowRunner) SetUserVar(userVar *UserVar) {$/;" f +SomeName tenets/codelingo/effective-go/single-method-interface-name/example.go /^type SomeName interface {$/;" t SomeUndocumented tenets/codelingo/go/golint/const-block.go /^ SomeUndocumented = 7 \/\/ MATCH \/SomeUndocumented.*should have comment.*block\/$/;" c -Something tenets/west/default/shadowing/project.cpp /^class Something {$/;" c file: -Stop tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Stop() error {$/;" f -Stop tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Stop() error {$/;" f +SomeWriter tenets/codelingo/effective-go/single-method-interface-name/example.go /^type SomeWriter interface {$/;" t +Something tenets/codelingo/west/shadowing/project.cpp /^class Something {$/;" c file: +Stop tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Stop() error {$/;" f +Stop tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Stop() error {$/;" f StrikeThrough flows/codelingo/docs/render/terminal.go /^func (options *Console) StrikeThrough(out *bytes.Buffer, text []byte) {$/;" f String flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) String() string {$/;" f String flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Hunk) String() string { return proto.CompactTextString(m) }$/;" f String flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (m *Request) String() string { return proto.CompactTextString(m) }$/;" f +String tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) String() string { return proto.CompactTextString(m) }$/;" f +String tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) String() string { return proto.CompactTextString(m) }$/;" f String tenets/codelingo/go/golint/common-methods.go /^func (T) String() string { return "" }$/;" f String tenets/codelingo/go/golint/var-decl.go /^func (q) String() string { return "I'm a q" }$/;" f Swap flows/codelingo/rewrite/rewrite/writer.go /^func (o byOffset) Swap(i, j int) {$/;" f @@ -233,12 +269,12 @@ T tenets/codelingo/go/golint/4.go /^type T int \/\/ MATCH \/exported type T.*sho T tenets/codelingo/go/golint/common-methods.go /^type T int$/;" t T tenets/codelingo/go/golint/sort.go /^type T []int$/;" t T tenets/codelingo/go/golint/unexp-return.go /^type T struct{}$/;" t -Tabby tenets/codelingo/k8/misused-nil-interface/example.go /^type Tabby struct{}$/;" t +Tabby tenets/codelingo/landmines/misused-nil-interface/example.go /^type Tabby struct{}$/;" t Table flows/codelingo/docs/render/terminal.go /^func (options *Console) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {}$/;" f TableCell flows/codelingo/docs/render/terminal.go /^func (options *Console) TableCell(out *bytes.Buffer, text []byte, flags int) {}$/;" f TableHeaderCell flows/codelingo/docs/render/terminal.go /^func (options *Console) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {}$/;" f TableRow flows/codelingo/docs/render/terminal.go /^func (options *Console) TableRow(out *bytes.Buffer, text []byte) {}$/;" f -TelcoNetworksController tenets/modica/default/transport/telco_networks/list.php /^class TelcoNetworksController extends Controller {$/;" c +TelcoNetworksController tenets/codelingo/modica/transport/telco_networks/list.php /^class TelcoNetworksController extends Controller {$/;" c Tenet util/mdgen/dataStruct/tenets.go /^type Tenet struct {$/;" t TenetDesc util/mdgen/cmd/listTenets.go /^type TenetDesc struct {$/;" t TenetsOwner util/mdgen/dataStruct/tenets.go /^type TenetsOwner struct {$/;" t @@ -248,16 +284,19 @@ Test flows/codelingo/rewrite/rewrite/package_test.go /^func Test(t *testing.T) { Test sdk/flow/package_test.go /^func Test(t *testing.T) {$/;" f TestAuthConfig tenets/codelingo/jenkinsx/tparallel/auth_integration_test.go /^func TestAuthConfig(t *testing.T) {$/;" f TestAuthConfigParallel tenets/codelingo/jenkinsx/tparallel/auth_integration_test.go /^func TestAuthConfigParallel(t *testing.T) {$/;" f +TestDecoratorArgs sdk/flow/cli_test.go /^func (s *flowSuite) TestDecoratorArgs(c *gc.C) {$/;" f TestDecoratorInput sdk/flow/cli_test.go /^func (s *flowSuite) TestDecoratorInput(c *gc.C) {$/;" f TestF tenets/codelingo/go/golint/errorf.go /^func TestF(t *testing.T) error {$/;" f -TestGetACat tenets/codelingo/k8/misused-nil-interface/example.go /^func TestGetACat(t *testing.T) {$/;" f TestHelloCorrect tenets/codelingo/jenkinsx/parallel-in-tests/hello_test.go /^func TestHelloCorrect(t *testing.T) {$/;" f TestHelloIncorrect tenets/codelingo/jenkinsx/parallel-in-tests/hello_test.go /^func TestHelloIncorrect(t *testing.T) { \/\/ ISSUE$/;" f TestHelloIntegration tenets/codelingo/jenkinsx/parallel-in-tests/hello_integration_test.go /^func TestHelloIntegration(t *testing.T) { \/\/ Shouldn't match$/;" f TestNewFile flows/codelingo/rewrite/rewrite/writer_test.go /^func (s *cmdSuite) TestNewFile(c *gc.C) {$/;" f TestNewFileSRC flows/codelingo/rewrite/rewrite/writer_test.go /^func (s *cmdSuite) TestNewFileSRC(c *gc.C) {$/;" f +TestPB tenets/codelingo/lightning-network-daemon/rpc-responses-display-uniformly/example.go /^type TestPB struct {$/;" t +TestParseArgs sdk/flow/cli_test.go /^func (s *flowSuite) TestParseArgs(c *gc.C) {$/;" f TestParseURL flows/codelingo/review/review/review_test.go /^func (s *reviewSuite) TestParseURL(c *C) {$/;" f TestPrint tenets/codelingo/go/tested/main_test.go /^func TestPrint() {$/;" f +TestRewriteFileName flows/codelingo/rewrite/rewrite/writer_test.go /^func (s *cmdSuite) TestRewriteFileName(c *gc.C) {$/;" f TestSayB tenets/codelingo/go/tested/example_test.go /^func TestSayB(t *testing.T) {$/;" f TestSomething tenets/codelingo/go/golint/5_test.go /^func TestSomething(t *testing.T) {$/;" f TestSomething_suffix tenets/codelingo/go/golint/5_test.go /^func TestSomething_suffix(t *testing.T) {$/;" f @@ -270,6 +309,8 @@ Testoof tenets/codelingo/jenkinsx/intformat/src/oof/oof_integration_test.go /^fu Testooo tenets/codelingo/jenkinsx/intformat/src/ooo/oooinvalid.go /^func Testooo() {$/;" f Testopf tenets/codelingo/jenkinsx/intformat/src/opf/opf_integration_test.go /^func Testopf() {$/;" f Testopo tenets/codelingo/jenkinsx/intformat/src/opo/opoinvalid.go /^func Testopo() {$/;" f +Thing tenets/codelingo/effective-go/package-comment/example-good.go /^func Thing() {$/;" f +Thing tenets/codelingo/effective-go/package-comment/example1.go /^func Thing() {$/;" f Thing tenets/codelingo/go/golint/4.go /^const Thing = "wonderful"$/;" c TitleBlock flows/codelingo/docs/render/terminal.go /^func (options *Console) TitleBlock(out *bytes.Buffer, text []byte) {$/;" f TripleEmphasis flows/codelingo/docs/render/terminal.go /^func (options *Console) TripleEmphasis(out *bytes.Buffer, text []byte) {$/;" f @@ -277,33 +318,46 @@ U tenets/codelingo/go/golint/4.go /^type U string$/;" t U tenets/codelingo/go/golint/sort.go /^type U []int$/;" t UNORDERED flows/codelingo/docs/render/terminal.go /^ UNORDERED = 1 << iota$/;" c UndocAgain tenets/codelingo/go/golint/const-block.go /^const UndocAgain = 6 \/\/ MATCH \/UndocAgain.*should have comment\/$/;" c -Unwatch tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Unwatch(collection string, id interface{}, ch chan<- Change) {$/;" f -Unwatch tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Unwatch(collection string, id interface{}, ch chan<- Change) {$/;" f -UnwatchCollection tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) UnwatchCollection(collection string, ch chan<- Change) {$/;" f -UnwatchCollection tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) UnwatchCollection(collection string, ch chan<- Change) {$/;" f -Update tenets/west/default/shadowing/project.cpp /^int Update(Object* object, std::vector somethings) {$/;" f +Unnamed tenets/codelingo/effective-go/comment-first-word-when-empty/example.go /^func Unnamed() {}$/;" f +Unwatch tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Unwatch(collection string, id interface{}, ch chan<- Change) {$/;" f +Unwatch tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Unwatch(collection string, id interface{}, ch chan<- Change) {$/;" f +UnwatchCollection tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) UnwatchCollection(collection string, ch chan<- Change) {$/;" f +UnwatchCollection tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) UnwatchCollection(collection string, ch chan<- Change) {$/;" f +Update tenets/codelingo/west/shadowing/project.cpp /^int Update(Object* object, std::vector somethings) {$/;" f +UserVar sdk/flow/user_variable.go /^type UserVar struct {$/;" t V tenets/codelingo/go/golint/4.go /^type V string$/;" t W tenets/codelingo/go/golint/4.go /^var W = "foo" \/\/ MATCH \/exported var W.*should.*comment.*or.*unexport\/$/;" v -Wait tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Wait() error {$/;" f -Wait tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Wait() error {$/;" f -Watch tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) Watch(collection string, id interface{}, revno int64, ch chan<- Change) {$/;" f -Watch tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) Watch(collection string, id interface{}, revno int64, ch chan<- Change) {$/;" f -WatchCollection tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) WatchCollection(collection string, ch chan<- Change) {$/;" f -WatchCollection tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) WatchCollection(collection string, ch chan<- Change) {$/;" f -WatchCollectionWithFilter tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) WatchCollectionWithFilter(collection string, ch chan<- Change, filter func(interface{}) bool) {$/;" f -WatchCollectionWithFilter tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) WatchCollectionWithFilter(collection string, ch chan<- Change, filter func(interface{}) bool) {$/;" f +Wait tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Wait() error {$/;" f +Wait tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Wait() error {$/;" f +Watch tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) Watch(collection string, id interface{}, revno int64, ch chan<- Change) {$/;" f +Watch tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) Watch(collection string, id interface{}, revno int64, ch chan<- Change) {$/;" f +WatchCollection tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) WatchCollection(collection string, ch chan<- Change) {$/;" f +WatchCollection tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) WatchCollection(collection string, ch chan<- Change) {$/;" f +WatchCollectionWithFilter tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) WatchCollectionWithFilter(collection string, ch chan<- Change, filter func(interface{}) bool) {$/;" f +WatchCollectionWithFilter tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) WatchCollectionWithFilter(collection string, ch chan<- Change, filter func(interface{}) bool) {$/;" f WhatDoesHeDo tenets/codelingo/go/golint/const-block.go /^ WhatDoesHeDo = "it's not a tumor!"$/;" c Whatsit tenets/codelingo/go/golint/const-block.go /^ Whatsit = "missing_comment" \/\/ MATCH \/Whatsit.*should have comment.*block\/$/;" c WhosYourDaddy tenets/codelingo/go/golint/const-block.go /^ WhosYourDaddy = "another_missing_one"$/;" c WhyAreYouUsingCapitalLetters_InACFunctionName tenets/codelingo/go/golint/names.go /^func WhyAreYouUsingCapitalLetters_InACFunctionName() {} \/\/ MATCH \/underscore.*func.*Why.*CFunctionName\/$/;" f Write flows/codelingo/rewrite/rewrite/writer.go /^func Write(results []*flowutil.DecoratedResult) error {$/;" f Write tenets/codelingo/go/golint/common-methods.go /^func (T) Write(p []byte) (n int, err error) { return 0, nil }$/;" f -WrongActionController tenets/modica/default/transport/wrong_action_name/something.php /^class WrongActionController extends Controller {$/;" c -WrongFilenameController tenets/modica/default/transport/wrong_filename/asdf.php /^class WrongFilenameController extends Controller {$/;" c -WrongNamespaceController tenets/modica/default/transport/wrong_namespace/something.php /^class WrongNamespaceController extends Controller {$/;" c +Writer tenets/codelingo/effective-go/single-method-interface-name/example.go /^type Writer interface {$/;" t +WrongActionController tenets/codelingo/modica/transport/wrong_action_name/something.php /^class WrongActionController extends Controller {$/;" c +WrongFilenameController tenets/codelingo/modica/transport/wrong_filename/asdf.php /^class WrongFilenameController extends Controller {$/;" c +WrongNamespaceController tenets/codelingo/modica/transport/wrong_namespace/something.php /^class WrongNamespaceController extends Controller {$/;" c X tenets/codelingo/go/golint/4.go /^const X = "bar" \/\/ MATCH \/exported const X.*should.*comment.*or.*unexport\/$/;" c X509B tenets/codelingo/go/golint/names.go /^ X509B = 4 \/\/ ditto$/;" c +XXX_DiscardUnknown tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) XXX_DiscardUnknown() {$/;" f +XXX_DiscardUnknown tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) XXX_DiscardUnknown() {$/;" f +XXX_Marshal tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {$/;" f +XXX_Marshal tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {$/;" f +XXX_Merge tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (dst *HelloReply) XXX_Merge(src proto.Message) {$/;" f +XXX_Merge tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (dst *HelloRequest) XXX_Merge(src proto.Message) {$/;" f XXX_OneofFuncs flows/codelingo/rewrite/rpc/rewrite.pb.go /^func (*Request) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {$/;" f +XXX_Size tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) XXX_Size() int {$/;" f +XXX_Size tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) XXX_Size() int {$/;" f +XXX_Unmarshal tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloReply) XXX_Unmarshal(b []byte) error {$/;" f +XXX_Unmarshal tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func (m *HelloRequest) XXX_Unmarshal(b []byte) error {$/;" f Y tenets/codelingo/go/golint/4.go /^var Y, Z int \/\/ MATCH \/exported var Z.*own declaration\/$/;" v Z tenets/codelingo/go/golint/4.go /^var Y, Z int \/\/ MATCH \/exported var Z.*own declaration\/$/;" v _ flows/codelingo/review/review/review_test.go /^var _ = Suite(&reviewSuite{})$/;" v @@ -313,6 +367,13 @@ _ flows/codelingo/rewrite/rpc/rewrite.pb.go /^var _ = fmt.Errorf$/;" v _ flows/codelingo/rewrite/rpc/rewrite.pb.go /^var _ = math.Inf$/;" v _ flows/codelingo/rewrite/rpc/rewrite.pb.go /^var _ = proto.Marshal$/;" v _ sdk/flow/package_test.go /^var _ = gc.Suite(&flowSuite{})$/;" v +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^const _ = grpc.SupportPackageIsVersion4$/;" c +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^const _ = proto.ProtoPackageIsVersion2 \/\/ please upgrade the proto package$/;" c +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var _ = fmt.Errorf$/;" v +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var _ = math.Inf$/;" v +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var _ = proto.Marshal$/;" v +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var _ context.Context$/;" v +_ tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var _ grpc.ClientConn$/;" v _ tenets/codelingo/go/golint/4.go /^var Location, _ = time.LoadLocation("Europe\/Istanbul") \/\/ not Constantinople$/;" v _ tenets/codelingo/go/golint/blank-import-lib.go /^ _ ast.Node \/\/ for "go\/ast"$/;" v _ tenets/codelingo/go/golint/blank-import-lib.go /^ _ fmt.Stringer \/\/ for "fmt"$/;" v @@ -322,20 +383,29 @@ _ tenets/codelingo/go/golint/blank-import-main.go /^var _ os.File \/\/ for "os"$ _ tenets/codelingo/go/golint/import-dot.go /^var _ Stringer \/\/ from "fmt"$/;" v _ tenets/codelingo/go/golint/var-decl.go /^var _ Server = (*serverImpl)(nil)$/;" v _ tenets/codelingo/go/init/example.go /^var _ = 0/;" v +_Greeter_SayHello_Handler tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {$/;" f +_Greeter_serviceDesc tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var _Greeter_serviceDesc = grpc.ServiceDesc{$/;" v _Request_OneofMarshaler flows/codelingo/rewrite/rpc/rewrite.pb.go /^func _Request_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {$/;" f _Request_OneofSizer flows/codelingo/rewrite/rpc/rewrite.pb.go /^func _Request_OneofSizer(msg proto.Message) (n int) {$/;" f _Request_OneofUnmarshaler flows/codelingo/rewrite/rpc/rewrite.pb.go /^func _Request_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {$/;" f __construct tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ public function __construct() {$/;" f +aFunc tenets/codelingo/code-review-comments/context-first-arg/example.go /^func aFunc(ctx context.Context, a int) {$/;" f +aFunction tenets/codelingo/effective-go/underscores-in-name/example.go /^func aFunction() {$/;" f aStruct tenets/codelingo/go/tested/example.go /^type aStruct struct {$/;" t +a_bad_function tenets/codelingo/effective-go/underscores-in-name/example.go /^func a_bad_function() {$/;" f +a_really_long_and_bad_package_name tenets/codelingo/effective-go/good-package-name/a_really_long_and_bad_package_name/example-bad.go /^package a_really_long_and_bad_package_name$/;" p action sdk/flow/cli.go /^func (f *flowRunner) action(ctx *cli.Context) {$/;" f action sdk/flow/confirm.go /^func (c *ConfirmerItem) action(option string) (func() (bool, bool, error), error) {$/;" f addOne tenets/codelingo/go/golint/inc.go /^func addOne(x int) int {$/;" f addOption flows/codelingo/rewrite/rewrite/option/option.go /^func (o *option) addOption(opt option) {$/;" f -anyStruct tenets/codelingo/k8/well-named-lock/example.go /^type anyStruct struct {$/;" t -anyStruct tenets/codelingo/k8/well-named-locks/example.go /^type anyStruct struct {$/;" t +andOneMoree tenets/codelingo/psr1/camel-case-method-name/example.php /^ function andOneMoree() {$/;" f +anotherTestFunc tenets/codelingo/landmines/shadowed-func-parameter/example.go /^func anotherTestFunc(strA string) string {$/;" f +anyStruct tenets/codelingo/kubernetes/well-named-lock/example.go /^type anyStruct struct {$/;" t +anyStruct tenets/codelingo/kubernetes/well-named-locks/example.go /^type anyStruct struct {$/;" t area tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^func (c circle) area() float64 {$/;" f area tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^func (r rect) area() float64 {$/;" f -asdfffsAction tenets/modica/default/transport/wrong_action_name/something.php /^ public function asdfffsAction(){} \/\/ ISSUE$/;" f +asdfffsAction tenets/codelingo/modica/transport/wrong_action_name/something.php /^ public function asdfffsAction(){} \/\/ ISSUE$/;" f +bFunc tenets/codelingo/code-review-comments/context-first-arg/example.go /^func bFunc(b int, ctx context.Context, a int) {$/;" f bStruct tenets/codelingo/go/tested/example.go /^type bStruct struct {$/;" t badArea tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^func (c badCircle) badArea() float64 {$/;" f badArea tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^func (r badRect) badArea() float64 {$/;" f @@ -345,11 +415,14 @@ badMeasure tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^ badPerim tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^func (c badCircle) badPerim() float64 {$/;" f badPerim tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^func (r badRect) badPerim() float64 {$/;" f badRect tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^type badRect struct {$/;" t -badStruct tenets/codelingo/k8/well-named-lock/example.go /^type badStruct struct {$/;" t -badStruct tenets/codelingo/k8/well-named-locks/example.go /^type badStruct struct {$/;" t +badStruct tenets/codelingo/kubernetes/well-named-lock/example.go /^type badStruct struct {$/;" t +badStruct tenets/codelingo/kubernetes/well-named-locks/example.go /^type badStruct struct {$/;" t +badpack tenets/codelingo/lightning-network-daemon/exported-package-is-tested/badpack/example-bad.go /^package badpack$/;" p +bar tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func bar() {}$/;" f bar tenets/codelingo/go/golint/receiver-names.go /^type bar struct{}$/;" t -bar tenets/codelingo/k8/well-named-package/foo/example-bad.go /^package bar$/;" p -baz tenets/codelingo/k8/well-named-package/baz/example-good.go /^package baz$/;" p +bar tenets/codelingo/kubernetes/well-named-package/foo/example-bad.go /^package bar$/;" p +baz tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func baz() {}$/;" f +baz tenets/codelingo/kubernetes/well-named-package/baz/example-good.go /^package baz$/;" p buildTenetList util/mdgen/cmd/listTenets.go /^func buildTenetList(hubtenets dataStruct.HubTenets) []*TenetDesc {$/;" f bundleURL flows/codelingo/docs/docs/parse/parse.go /^const bundleURL = "https:\/\/raw.githubusercontent.com\/codelingo\/codelingo\/master\/tenets\/%s\/%s\/lingo_bundle.yaml"$/;" c byOffset flows/codelingo/rewrite/rewrite/writer.go /^type byOffset []*flowutil.DecoratedResult$/;" t @@ -357,6 +430,7 @@ byUserPriority util/mdgen/cmd/listTenets.go /^type byUserPriority []*TenetDesc$/ c1 tenets/codelingo/go/global-var/example.go /^const c1 = 0$/;" c c2 tenets/codelingo/go/global-var/example.go /^ c2 = 1$/;" c c3 tenets/codelingo/go/global-var/example.go /^ c3 = 2$/;" c +cFunc tenets/codelingo/code-review-comments/context-first-arg/example.go /^func cFunc(c int, b int, ctx context.Context, a int) {$/;" f case1_1 tenets/codelingo/go/golint/names.go /^const case1_1 = 1$/;" c case2_1 tenets/codelingo/go/golint/names.go /^type case2_1 struct {$/;" t case3_1 tenets/codelingo/go/golint/names.go /^func case3_1(case3_2 int) (case3_3 string) {$/;" f @@ -374,19 +448,20 @@ cmd util/mdgen/cmd/write.go /^package cmd$/;" p cmdSuite flows/codelingo/rewrite/rewrite/package_test.go /^type cmdSuite struct {$/;" t code tenets/codelingo/go/init/example.go /^package code$/;" p command sdk/flow/cli.go /^func (f *flowRunner) command(ctx *cli.Context) (err error) {$/;" f +comment flows/codelingo/rewrite/rewrite/writer.go /^type comment struct {$/;" t constant tenets/codelingo/go/init/example.go /^const constant = 0$/;" c contextKeyTypeTests tenets/codelingo/go/golint/contextkeytypes.go /^func contextKeyTypeTests() {$/;" f contextkeytypes tenets/codelingo/go/golint/contextkeytypes.go /^package contextkeytypes$/;" p copyFlag sdk/flow/cli.go /^func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {$/;" f -correct tenets/cacophony/default/caught-generic-exceptions/example.py /^def correct():$/;" f -correct tenets/cacophony/default/debug-prints/example.py /^def correct(a, b):$/;" f -correct tenets/cacophony/default/no-fmt-print/example.go /^func correct() {$/;" f -correct tenets/cacophony/default/raise-generic-exceptions/example.py /^def correct():$/;" f -correct tenets/cacophony/default/redundant-defer-wraps/example.go /^func correct() {$/;" f +correct tenets/codelingo/cacophony/caught-generic-exceptions/example.py /^def correct():$/;" f +correct tenets/codelingo/cacophony/debug-prints/example.py /^def correct(a, b):$/;" f +correct tenets/codelingo/cacophony/no-fmt-print/example.go /^func correct() {$/;" f +correct tenets/codelingo/cacophony/raise-generic-exceptions/example.py /^def correct():$/;" f +correct tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^func correct() {$/;" f correct tenets/codelingo/go/println-format-strings/main.go /^func correct() {$/;" f -correct1 tenets/cacophony/default/defer-in-loop/example.go /^func correct1() {$/;" f -correct2 tenets/cacophony/default/defer-in-loop/example.go /^func correct2() {$/;" f -correctAliased tenets/cacophony/default/no-fmt-print/example-aliased.go /^func correctAliased() {$/;" f +correct1 tenets/codelingo/cacophony/defer-in-loop/example.go /^func correct1() {$/;" f +correct2 tenets/codelingo/cacophony/defer-in-loop/example.go /^func correct2() {$/;" f +correctAliased tenets/codelingo/cacophony/no-fmt-print/example-aliased.go /^func correctAliased() {$/;" f crazyID tenets/codelingo/go/golint/var-decl.go /^var crazyID int64 = -(-(-(-9)))$/;" v ctrl tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^$ctrl = new Controller();$/;" v ctxKey tenets/codelingo/go/golint/contextkeytypes.go /^type ctxKey struct{}$/;" t @@ -395,11 +470,15 @@ dataStruct util/mdgen/dataStruct/tenets.go /^package dataStruct$/;" p decHelp sdk/flow/help.go /^type decHelp struct {$/;" t decoratorAction flows/codelingo/review/review/decorator.go /^func decoratorAction(ctx *cli.Context, payload proto.Message) (bool, error) {$/;" f decoratorAction flows/codelingo/rewrite/rewrite/decorator.go /^func decoratorAction(ctx *cli.Context, payload proto.Message) (bool, error) {$/;" f +decoratorArgsTest sdk/flow/cli_test.go /^var decoratorArgsTest = []struct {$/;" v decoratorCMD sdk/flow/cli_test.go /^var decoratorCMD = &DecoratorApp{$/;" v defaultTemplate flows/codelingo/docs/main.go /^var defaultTemplate string = `$/;" v -doStuff tenets/codelingo/go/sprintf/example.go /^func doStuff(param string) error {$/;" f -doThis tenets/codelingo/go/bool-param/example.go /^func doThis() {}$/;" f -doThisOrThat tenets/codelingo/go/bool-param/example.go /^func doThisOrThat(flag bool) {$/;" f +doStuffA tenets/codelingo/go/sprintf/example.go /^func doStuffA(param string) error {$/;" f +doStuffN tenets/codelingo/go/sprintf/example.go /^func doStuffN(param string) error {$/;" f +doThat tenets/codelingo/go/bool-param/example.go /^func doThat() {}$/;" f +doThis tenets/codelingo/go/bool-param/example.go /^func doThis() (flag bool) {}$/;" f +doThisOrThat tenets/codelingo/go/bool-param/example.go /^func doThisOrThat(flag bool) bool { \/\/ Issue$/;" f +doThisOrThatNoArg tenets/codelingo/go/bool-param/example.go /^func doThisOrThatNoArg() bool { \/\/ Issue$/;" f docMaps flows/codelingo/docs/main.go /^func docMaps(ctx *cli.Context) ([]map[string]string, error) {$/;" f docs flows/codelingo/docs/docs/docs.go /^package docs$/;" p docsAction flows/codelingo/docs/main.go /^func docsAction(ctx *cli.Context) {$/;" f @@ -421,7 +500,8 @@ dpo tenets/codelingo/jenkinsx/intformat/src/dpo/dpo.go /^package dpo$/;" p dpo_test tenets/codelingo/jenkinsx/intformat/src/dpo/dpoinvalid.go /^package dpo_test$/;" p e1 tenets/codelingo/go/golint/errors.go /^ e1 = fmt.Errorf("blah %d", 4) \/\/ MATCH \/error var.*e1.*errFoo\/$/;" v emphasisStyles flows/codelingo/docs/render/terminal.go /^var emphasisStyles = [...]string{$/;" v -example tenets/codelingo/go/nil_only_functions/example.go /^type example struct {$/;" t +example tenets/codelingo/code-review-comments/do-not-discard-errors/example.go /^func example() (int, error) {$/;" f +example tenets/codelingo/go/nil-only-functions/example.go /^type example struct {$/;" t exampleFunc tenets/codelingo/php/phplint/BadExample.php /^ function exampleFunc() {$/;" f exampleFunc tenets/codelingo/php/phplint/GoodExample.php /^ function exampleFunc()$/;" f exported_to_c tenets/codelingo/go/golint/names.go /^func exported_to_c() {} \/\/ okay: https:\/\/github.com\/golang\/lint\/issues\/144$/;" f @@ -451,9 +531,11 @@ f6 tenets/codelingo/go/golint/receiver-names.go /^func (bar) f6() {$/;" f f7 tenets/codelingo/go/golint/receiver-names.go /^func (_ *bar) f7() { \/\/ MATCH \/receiver name should not be an underscore\/$/;" f f8 tenets/codelingo/go/golint/receiver-names.go /^func (me multiError) f8() {$/;" f f_it tenets/codelingo/go/golint/names.go /^func f_it() { \/\/ MATCH \/underscore.*func.*f_it\/$/;" f +fanOutUserVars sdk/flow/user_variable.go /^func fanOutUserVars(incoming <-chan *grpcflow.Reply, flowsetterc chan<- *grpcflow.Request) (chan *grpcflow.Reply, <-chan *UserVar, chan error) {$/;" f fileDescriptor0 flows/codelingo/rewrite/rpc/rewrite.pb.go /^var fileDescriptor0 = []byte{$/;" v -flagBadName tenets/codelingo/k8/flags-have-underscores/example.go /^ flagBadName = "bad_name"$/;" c -flagGoodName tenets/codelingo/k8/flags-have-underscores/example.go /^ flagGoodName = "good-name"$/;" c +fileDescriptor_helloworld_71e208cbdc16936b tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var fileDescriptor_helloworld_71e208cbdc16936b = []byte{$/;" v +flagBadName tenets/codelingo/kubernetes/flags-have-underscores/example.go /^ flagBadName = "bad_name"$/;" c +flagGoodName tenets/codelingo/kubernetes/flags-have-underscores/example.go /^ flagGoodName = "good-name"$/;" c flags tenets/codelingo/go/golint/var-decl.go /^var flags uint32 = num$/;" v floatT tenets/codelingo/go/golint/var-decl.go /^type floatT float64$/;" t floatV tenets/codelingo/go/golint/var-decl.go /^var floatV floatT = 123.45$/;" v @@ -464,12 +546,15 @@ flow sdk/flow/git.go /^package flow$/;" p flow sdk/flow/help.go /^package flow$/;" p flow sdk/flow/package_test.go /^package flow$/;" p flow sdk/flow/request.go /^package flow$/;" p +flow sdk/flow/user_variable.go /^package flow$/;" p flow sdk/flow/util.go /^package flow$/;" p flowRunner sdk/flow/cli.go /^type flowRunner struct {$/;" t flowSuite sdk/flow/package_test.go /^type flowSuite struct {$/;" t flows flows/codelingo/pull-request/main.go /^package flows$/;" p -flush tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) flush() {$/;" f -flush tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) flush() {$/;" f +flush tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) flush() {$/;" f +flush tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) flush() {$/;" f +foo tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func foo() {}$/;" f +foo tenets/codelingo/effective-go/good-package-name/main/example-good.go /^package foo$/;" p foo tenets/codelingo/go/golint/blank-import-lib.go /^package foo$/;" p foo tenets/codelingo/go/golint/blank-import-lib_test.go /^package foo$/;" p foo tenets/codelingo/go/golint/const-block.go /^package foo$/;" p @@ -488,8 +573,9 @@ foo tenets/codelingo/go/golint/receiver-names.go /^type foo struct{}$/;" t foo tenets/codelingo/go/golint/time.go /^package foo$/;" p foo tenets/codelingo/go/golint/unexp-return.go /^package foo$/;" p foo tenets/codelingo/go/golint/var-decl.go /^package foo$/;" p -foo tenets/modica/default/null-check/example.php /^$foo = NULL;$/;" v +foo tenets/codelingo/modica/null-check/example.php /^$foo = NULL;$/;" v fooId tenets/codelingo/go/golint/names.go /^const fooId = "blah" \/\/ MATCH \/fooId.*fooID\/$/;" c +foo_test tenets/codelingo/effective-go/good-package-name/main/example-test-package.go /^package foo_test$/;" p formattedDocsFromTenets flows/codelingo/docs/main.go /^func formattedDocsFromTenets(ctx *cli.Context) (string, error) {$/;" f function tenets/codelingo/go/init/example.go /^func function() {$/;" f function tenets/codelingo/go/init/example.go /^func function(arg string) bool {$/;" f @@ -507,6 +593,7 @@ genLexiconCmd util/mdgen/cmd/genLexicon.go /^var genLexiconCmd = &cobra.Command{ genTenetsCmd util/mdgen/cmd/genTenets.go /^var genTenetsCmd = &cobra.Command{$/;" v geometry tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^type geometry interface {$/;" t get tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ public function get () {$/;" f +getACat tenets/codelingo/landmines/misused-nil-interface/example.go /^func getACat() Cat {$/;" f getLingoFilepaths flows/codelingo/docs/docs/docs.go /^func getLingoFilepaths(workingDir string) ([]string, error) {$/;" f getUserFromSession tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ public function getUserFromSession() {$/;" f gitCMD flows/codelingo/docs/docs/docs.go /^func gitCMD(args ...string) (out string, err error) {$/;" f @@ -514,6 +601,7 @@ github404 flows/codelingo/docs/docs/parse/parse.go /^const github404 = "404: Not goodArea tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^func (c goodCircle) goodArea() float64 {$/;" f goodArea tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^func (r goodRect) goodArea() float64 {$/;" f goodCircle tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^type goodCircle struct {$/;" t +goodFunc tenets/codelingo/landmines/shadowed-func-parameter/example.go /^func goodFunc(strA string) string {$/;" f goodGeometry tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^type goodGeometry interface {$/;" t goodGoodArea tenets/codelingo/jenkinsx/orginterfaces/interface.go /^func (c goodGoodCircle) goodGoodArea() float64 {$/;" f goodGoodArea tenets/codelingo/jenkinsx/orginterfaces/interface.go /^func (r goodGoodRect) goodGoodArea() float64 {$/;" f @@ -527,21 +615,27 @@ goodMeasure tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^func goodPerim tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^func (c goodCircle) goodPerim() float64 {$/;" f goodPerim tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^func (r goodRect) goodPerim() float64 {$/;" f goodRect tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^type goodRect struct {$/;" t -goodpack tenets/codelingo/k8/new-package-requires-test/badpack/example-bad.go /^package goodpack$/;" p -goodpack tenets/codelingo/k8/new-package-requires-test/goodpack/example-good.go /^package goodpack$/;" p +goodpack tenets/codelingo/kubernetes/new-package-requires-test/badpack/example-bad.go /^package goodpack$/;" p +goodpack tenets/codelingo/kubernetes/new-package-requires-test/goodpack/example-good.go /^package goodpack$/;" p +goodpack tenets/codelingo/lightning-network-daemon/exported-package-is-tested/goodpack/example-good.go /^package goodpack$/;" p googleIPs tenets/codelingo/go/golint/var-decl.go /^var googleIPs []int$/;" v +greeterClient tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^type greeterClient struct {$/;" t h tenets/codelingo/go/golint/error-return.go /^func h() int { \/\/ ok$/;" f h tenets/codelingo/go/golint/iferr.go /^func h() error {$/;" f -handle tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) handle(req interface{}) {$/;" f -handle tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) handle(req interface{}) {$/;" f +handle tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) handle(req interface{}) {$/;" f +handle tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) handle(req interface{}) {$/;" f hasOption flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) hasOption(opt option) bool {$/;" f headerStyles flows/codelingo/docs/render/terminal.go /^var headerStyles = [...]string{$/;" v hello tenets/codelingo/jenkinsx/parallel-in-tests/hello.go /^package hello$/;" p hello tenets/codelingo/jenkinsx/test-package-name/hello.go /^package hello$/;" p hello tenets/codelingo/jenkinsx/test-package-name/hello_incorrect_test.go /^package hello \/\/ ISSUE$/;" p +helloOtherWorldd tenets/codelingo/psr1/camel-case-method-name/example.php /^ function helloOtherWorldd() {$/;" f +helloWorld tenets/codelingo/psr1/camel-case-method-name/example.php /^ function helloWorld() {$/;" f +helloWorld tenets/codelingo/psr2/elseif-not-else-if/example.php /^function helloWorld() {$/;" f hello_test tenets/codelingo/jenkinsx/parallel-in-tests/hello_integration_test.go /^package hello_test$/;" p hello_test tenets/codelingo/jenkinsx/parallel-in-tests/hello_test.go /^package hello_test$/;" p hello_test tenets/codelingo/jenkinsx/test-package-name/hello_test.go /^package hello_test$/;" p +helloworld tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^package helloworld$/;" p helpData sdk/flow/help.go /^type helpData struct {$/;" t hidden tenets/codelingo/go/golint/unexp-return.go /^type hidden struct{}$/;" t hubFrontend util/serve/main.go /^const hubFrontend = "http:\/\/localhost:8080"$/;" c @@ -549,18 +643,20 @@ i tenets/codelingo/go/golint/error-return.go /^func i() (int, error) { \/\/ ok$/ i tenets/codelingo/go/golint/iferr.go /^func i() error {$/;" f i tenets/codelingo/go/golint/names.go /^type i interface {$/;" t i tenets/codelingo/php/count-in-loop/test.php /^$i = 0;$/;" v -incorrect tenets/cacophony/default/debug-prints/example.py /^def incorrect(a, b):$/;" f -incorrect tenets/cacophony/default/defer-in-loop/example.go /^func incorrect() {$/;" f -incorrect tenets/cacophony/default/no-fmt-print/example.go /^func incorrect() {$/;" f -incorrect tenets/cacophony/default/raise-generic-exceptions/example.py /^def incorrect():$/;" f -incorrect tenets/cacophony/default/redundant-defer-wraps/example.go /^func incorrect() {$/;" f +incorrect tenets/codelingo/cacophony/debug-prints/example.py /^def incorrect(a, b):$/;" f +incorrect tenets/codelingo/cacophony/defer-in-loop/example.go /^func incorrect() {$/;" f +incorrect tenets/codelingo/cacophony/no-fmt-print/example.go /^func incorrect() {$/;" f +incorrect tenets/codelingo/cacophony/raise-generic-exceptions/example.py /^def incorrect():$/;" f +incorrect tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^func incorrect() {$/;" f incorrect tenets/codelingo/go/println-format-strings/main.go /^func incorrect() {$/;" f -incorrect1 tenets/cacophony/default/caught-generic-exceptions/example.py /^def incorrect1():$/;" f -incorrect2 tenets/cacophony/default/caught-generic-exceptions/example.py /^def incorrect2():$/;" f -incorrectAliased tenets/cacophony/default/no-fmt-print/example-aliased.go /^func incorrectAliased() {$/;" f +incorrect1 tenets/codelingo/cacophony/caught-generic-exceptions/example.py /^def incorrect1():$/;" f +incorrect2 tenets/codelingo/cacophony/caught-generic-exceptions/example.py /^def incorrect2():$/;" f +incorrectAliased tenets/codelingo/cacophony/no-fmt-print/example-aliased.go /^func incorrectAliased() {$/;" f indent flows/codelingo/review/review/decorator.go /^func indent(str string, add, remove bool) string {$/;" f indent flows/codelingo/rewrite/rewrite/decorator.go /^func indent(str string, add, remove bool) string {$/;" f init flows/codelingo/rewrite/rpc/rewrite.pb.go /^func init() {$/;" f +init tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_helloworld_71e208cbdc16936b) }$/;" f +init tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^func init() {$/;" f init tenets/codelingo/go/init/example.go /^func (s S) init(arg string) bool {$/;" f init tenets/codelingo/go/init/example.go /^func init() {$/;" f init util/mdgen/cmd/genAll.go /^func init() {$/;" f @@ -571,9 +667,9 @@ init util/mdgen/cmd/listTenets.go /^func init() {$/;" f init util/mdgen/cmd/root.go /^func init() {$/;" f init util/mdgen/cmd/write.go /^func init() {$/;" f initConfig util/mdgen/cmd/root.go /^func initConfig() {$/;" f -inner tenets/west/default/shadowing/project.cpp /^ Object* inner;$/;" m class:Object file: +inner tenets/codelingo/west/shadowing/project.cpp /^ Object* inner;$/;" m class:Object file: int tenets/codelingo/go/golint/unexp-return.go /^type int struct{}$/;" t -intFunc tenets/codelingo/go/nil_only_functions/example.go /^func (example) intFunc() error {$/;" f +intFunc tenets/codelingo/go/nil-only-functions/example.go /^func (example) intFunc() error {$/;" f invalidator tenets/codelingo/jenkinsx/intformat/generate_tests.go /^var invalidator = "invalid"$/;" v inverse tenets/codelingo/php/empty-catch-block/test.php /^function inverse($x) {$/;" f isLingoFile flows/codelingo/docs/docs/docs.go /^func isLingoFile(file string) bool {$/;" f @@ -593,13 +689,13 @@ lexInfo util/mdgen/cmd/genLexicon.go /^type lexInfo struct {$/;" t lineOffsets flows/codelingo/rewrite/rewrite/writer.go /^func lineOffsets(src []byte, offset int32) []int32 {$/;" f linkStyle flows/codelingo/docs/render/terminal.go /^var linkStyle = ansi.ColorCode("015+u")$/;" v list flows/codelingo/docs/render/terminal.go /^type list struct {$/;" t -listAction tenets/modica/default/transport/telco_networks/list.php /^ public function listAction(){} \/\/ Fits Template$/;" f +listAction tenets/codelingo/modica/transport/telco_networks/list.php /^ public function listAction(){} \/\/ Fits Template$/;" f listFacts util/mdgen/cmd/genLexicon.go /^func listFacts(owner, lexName string) (map[string][]string, error) {$/;" f listLexs util/mdgen/cmd/genLexicon.go /^func listLexs() ([]*lexInfo, error) {$/;" f listTenetsCmd util/mdgen/cmd/listTenets.go /^var listTenetsCmd = &cobra.Command{$/;" v logoutUrl tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ $logoutUrl = "\/logout";$/;" v -loop tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) loop() error {$/;" f -loop tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) loop() error {$/;" f +loop tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) loop() error {$/;" f +loop tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) loop() error {$/;" f m tenets/codelingo/go/golint/error-return.go /^func m() (x int, err error, y int) { \/\/ MATCH \/error should be the last type\/$/;" f m tenets/codelingo/go/golint/iferr.go /^func m() error {$/;" f main flows/codelingo/docs/main.go /^func main() {$/;" f @@ -612,17 +708,45 @@ main flows/codelingo/rewrite/main.go /^package main$/;" p main flows/codelingo/rewrite/rewrite/test/mock.go /^func main() {$/;" f main flows/codelingo/search/main.go /^func main() {$/;" f main flows/codelingo/search/main.go /^package main$/;" p -main tenets/cacophony/default/caught-generic-exceptions/example.py /^def main():$/;" f -main tenets/cacophony/default/debug-prints/example.py /^def main():$/;" f -main tenets/cacophony/default/defer-in-loop/example.go /^func main() {$/;" f -main tenets/cacophony/default/defer-in-loop/example.go /^package main$/;" p -main tenets/cacophony/default/no-fmt-print/example-aliased.go /^func main() {$/;" f -main tenets/cacophony/default/no-fmt-print/example-aliased.go /^package main$/;" p -main tenets/cacophony/default/no-fmt-print/example.go /^func main() {$/;" f -main tenets/cacophony/default/no-fmt-print/example.go /^package main$/;" p -main tenets/cacophony/default/raise-generic-exceptions/example.py /^def main():$/;" f -main tenets/cacophony/default/redundant-defer-wraps/example.go /^func main() {$/;" f -main tenets/cacophony/default/redundant-defer-wraps/example.go /^package main$/;" p +main tenets/codelingo/cacophony/caught-generic-exceptions/example.py /^def main():$/;" f +main tenets/codelingo/cacophony/debug-prints/example.py /^def main():$/;" f +main tenets/codelingo/cacophony/defer-in-loop/example.go /^func main() {$/;" f +main tenets/codelingo/cacophony/defer-in-loop/example.go /^package main$/;" p +main tenets/codelingo/cacophony/no-fmt-print/example-aliased.go /^func main() {$/;" f +main tenets/codelingo/cacophony/no-fmt-print/example-aliased.go /^package main$/;" p +main tenets/codelingo/cacophony/no-fmt-print/example.go /^func main() {$/;" f +main tenets/codelingo/cacophony/no-fmt-print/example.go /^package main$/;" p +main tenets/codelingo/cacophony/raise-generic-exceptions/example.py /^def main():$/;" f +main tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^func main() {$/;" f +main tenets/codelingo/cacophony/redundant-defer-wraps/example.go /^package main$/;" p +main tenets/codelingo/cockroachdb/fmt-verbs/example.go /^func main() {$/;" f +main tenets/codelingo/cockroachdb/fmt-verbs/example.go /^package main$/;" p +main tenets/codelingo/cockroachdb/func-args-inline-comments/example.go /^func main() {$/;" f +main tenets/codelingo/cockroachdb/func-args-inline-comments/example.go /^package main$/;" p +main tenets/codelingo/cockroachdb/line-length-limit/example.go /^func main() {$/;" f +main tenets/codelingo/cockroachdb/line-length-limit/example.go /^package main$/;" p +main tenets/codelingo/code-review-comments/avoid-renaming-imports/example.go /^func main() {$/;" f +main tenets/codelingo/code-review-comments/avoid-renaming-imports/example.go /^package main$/;" p +main tenets/codelingo/code-review-comments/avoid-renaming-imports/example2.go /^func main() {$/;" f +main tenets/codelingo/code-review-comments/avoid-renaming-imports/example2.go /^package main$/;" p +main tenets/codelingo/code-review-comments/context-first-arg/example.go /^package main$/;" p +main tenets/codelingo/code-review-comments/do-not-discard-errors/example.go /^func main() {$/;" f +main tenets/codelingo/code-review-comments/do-not-discard-errors/example.go /^package main$/;" p +main tenets/codelingo/code-review-comments/go-error-fmt/example.go /^func main() {$/;" f +main tenets/codelingo/code-review-comments/go-error-fmt/example.go /^package main$/;" p +main tenets/codelingo/code-review-comments/use-crypto-rand/example.go /^func main() {$/;" f +main tenets/codelingo/code-review-comments/use-crypto-rand/example.go /^package main$/;" p +main tenets/codelingo/effective-go/avoid-annotations-in-comments/example.go /^func main() {$/;" f +main tenets/codelingo/effective-go/avoid-annotations-in-comments/example.go /^package main$/;" p +main tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func main() {$/;" f +main tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^package main$/;" p +main tenets/codelingo/effective-go/comment-first-word-when-empty/example.go /^package main$/;" p +main tenets/codelingo/effective-go/package-comment/example-good.go /^package main$/;" p +main tenets/codelingo/effective-go/package-comment/example1.go /^package main$/;" p +main tenets/codelingo/effective-go/single-method-interface-name/example.go /^func main() {$/;" f +main tenets/codelingo/effective-go/single-method-interface-name/example.go /^package main$/;" p +main tenets/codelingo/effective-go/underscores-in-name/example.go /^func main() {$/;" f +main tenets/codelingo/effective-go/underscores-in-name/example.go /^package main$/;" p main tenets/codelingo/go/bool-param/example.go /^func main() {$/;" f main tenets/codelingo/go/bool-param/example.go /^package main$/;" p main tenets/codelingo/go/empty-slice/example.go /^func main() {$/;" f @@ -635,8 +759,8 @@ main tenets/codelingo/go/goto/example.go /^func main() {$/;" f main tenets/codelingo/go/goto/example.go /^package main$/;" p main tenets/codelingo/go/marshalling/example.go /^func main() {$/;" f main tenets/codelingo/go/marshalling/example.go /^package main$/;" p -main tenets/codelingo/go/nil_only_functions/example.go /^func main() {$/;" f -main tenets/codelingo/go/nil_only_functions/example.go /^package main$/;" p +main tenets/codelingo/go/nil-only-functions/example.go /^func main() {$/;" f +main tenets/codelingo/go/nil-only-functions/example.go /^package main$/;" p main tenets/codelingo/go/println-format-strings/main.go /^func main() {$/;" f main tenets/codelingo/go/println-format-strings/main.go /^package main$/;" p main tenets/codelingo/go/sprintf/example.go /^func main() {$/;" f @@ -646,10 +770,13 @@ main tenets/codelingo/go/tested/example_test.go /^package main$/;" p main tenets/codelingo/go/tested/main.go /^func main() {}$/;" f main tenets/codelingo/go/tested/main.go /^package main$/;" p main tenets/codelingo/go/tested/main_test.go /^package main$/;" p +main tenets/codelingo/go/ticker-in-for-switch/example.go /^func main() {$/;" f +main tenets/codelingo/go/ticker-in-for-switch/example.go /^package main$/;" p main tenets/codelingo/go/todo/example.go /^func main() {$/;" f main tenets/codelingo/go/todo/example.go /^package main$/;" p main tenets/codelingo/go/unconvert/example.go /^func main() {$/;" f main tenets/codelingo/go/unconvert/example.go /^package main$/;" p +main tenets/codelingo/go/unsafe-go-routine-variables/test.go /^package main$/;" p main tenets/codelingo/go/unused-private-functions/main/main.go /^func main() {$/;" f main tenets/codelingo/go/unused-private-functions/main/main.go /^package main$/;" p main tenets/codelingo/jenkinsx/intformat/generate_tests.go /^func main() {$/;" f @@ -659,18 +786,20 @@ main tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^package main$/;" main tenets/codelingo/jenkinsx/orginterfaces/example/bad-interface-2.go /^package main$/;" p main tenets/codelingo/jenkinsx/orginterfaces/example/interface.go /^package main$/;" p main tenets/codelingo/jenkinsx/orginterfaces/interface.go /^package main$/;" p -main tenets/codelingo/k8/flags-have-underscores/example.go /^package main$/;" p -main tenets/codelingo/k8/jsonapi-kind-compulsory-metadata/example.go /^package main$/;" p -main tenets/codelingo/k8/looped-vars-outside-loop/example.go /^func main() {$/;" f -main tenets/codelingo/k8/looped-vars-outside-loop/example.go /^package main$/;" p -main tenets/codelingo/k8/misused-nil-interface/example.go /^package main$/;" p -main tenets/codelingo/k8/shadowed-func-parameter/example.go /^package main$/;" p -main tenets/codelingo/k8/well-formed-lists/example.go /^package main$/;" p -main tenets/codelingo/k8/well-named-lists/example.go /^package main$/;" p -main tenets/codelingo/k8/well-named-lock/example.go /^package main$/;" p -main tenets/codelingo/k8/well-named-locks/example.go /^package main$/;" p -main tenets/west/default/shadowing/project.cpp /^int main() {$/;" f -main tenets/west/default/switch/switch.cpp /^int main() {$/;" f +main tenets/codelingo/kubernetes/flags-have-underscores/example.go /^package main$/;" p +main tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/example.go /^package main$/;" p +main tenets/codelingo/kubernetes/well-formed-lists/example.go /^package main$/;" p +main tenets/codelingo/kubernetes/well-named-lists/example.go /^package main$/;" p +main tenets/codelingo/kubernetes/well-named-lock/example.go /^package main$/;" p +main tenets/codelingo/kubernetes/well-named-locks/example.go /^package main$/;" p +main tenets/codelingo/landmines/looped-vars-outside-loop/example.go /^func main() {$/;" f +main tenets/codelingo/landmines/looped-vars-outside-loop/example.go /^package main$/;" p +main tenets/codelingo/landmines/misused-nil-interface/example.go /^func main() {$/;" f +main tenets/codelingo/landmines/misused-nil-interface/example.go /^package main$/;" p +main tenets/codelingo/landmines/shadowed-func-parameter/example.go /^package main$/;" p +main tenets/codelingo/lightning-network-daemon/rpc-responses-display-uniformly/example.go /^package main$/;" p +main tenets/codelingo/west/shadowing/project.cpp /^int main() {$/;" f +main tenets/codelingo/west/switch/switch.cpp /^int main() {$/;" f main util/mdgen/main.go /^func main() {$/;" f main util/mdgen/main.go /^package main$/;" p main util/serve/main.go /^func main() {$/;" f @@ -695,9 +824,9 @@ myZeroStr tenets/codelingo/go/golint/var-decl.go /^var myZeroStr string = "" n tenets/codelingo/go/golint/error-return.go /^func n() (int, error, error) { \/\/ OK$/;" f name tenets/codelingo/php/eval-expression/test.php /^$name = 'coffee';$/;" v negID tenets/codelingo/go/golint/var-decl.go /^var negID int64 = -1$/;" v -newFileSRC flows/codelingo/rewrite/rewrite/writer.go /^func newFileSRC(ctx *cli.Context, hunk *rewriterpc.Hunk, fileSRC []byte) ([]byte, error) {$/;" f -newHubWatcher tenets/codelingo/go/reallocated_slice/broken.go /^func newHubWatcher(hub HubSource, logger Logger) (*HubWatcher, <-chan struct{}) {$/;" f -newHubWatcher tenets/codelingo/go/reallocated_slice/fixed.go /^func newHubWatcher(hub HubSource, logger Logger) (*HubWatcher, <-chan struct{}) {$/;" f +newFileSRC flows/codelingo/rewrite/rewrite/writer.go /^func newFileSRC(ctx *cli.Context, hunk *rewriterpc.Hunk, fileSRC []byte) ([]byte, *comment, error) {$/;" f +newHubWatcher tenets/codelingo/go/reallocated-slice/broken.go /^func newHubWatcher(hub HubSource, logger Logger) (*HubWatcher, <-chan struct{}) {$/;" f +newHubWatcher tenets/codelingo/go/reallocated-slice/fixed.go /^func newHubWatcher(hub HubSource, logger Logger) (*HubWatcher, <-chan struct{}) {$/;" f newWriter tenets/codelingo/go/golint/var-decl.go /^func newWriter() io.Writer { return nil }$/;" f ni tenets/codelingo/go/golint/var-decl.go /^var ni nosuchpkg.Interface = nosuchpkg.NewConcrete()$/;" v normalizeFlags sdk/flow/cli.go /^func normalizeFlags(flags []cli.Flag, set *flag.FlagSet) error {$/;" f @@ -705,8 +834,8 @@ num tenets/codelingo/go/golint/var-decl.go /^const num = 123$/;" c o tenets/codelingo/go/golint/error-return.go /^func o() (int, error, int, error) { \/\/ OK$/;" f offset flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) offset() option {$/;" f offsiteCheck tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^function offsiteCheck($url) {$/;" f -oldSRC flows/codelingo/rewrite/rewrite/writer_test.go /^var oldSRC string = `$/;" v -onlyReturnsNil tenets/codelingo/go/nil_only_functions/example.go /^func onlyReturnsNil() error {$/;" f +oldSRC flows/codelingo/rewrite/rewrite/writer_test.go /^var oldSRC = `$/;" v +onlyReturnsNil tenets/codelingo/go/nil-only-functions/example.go /^func onlyReturnsNil() error {$/;" f oof tenets/codelingo/jenkinsx/intformat/src/oof/oof.go /^func oof() {}$/;" f oof tenets/codelingo/jenkinsx/intformat/src/oof/oof.go /^package oof$/;" p oofinvalid tenets/codelingo/jenkinsx/intformat/src/oof/oof_integration_test.go /^package oofinvalid$/;" p @@ -733,10 +862,12 @@ out tenets/codelingo/go/golint/var-decl.go /^var out io.Writer = os.Stdout$/;" v out2 tenets/codelingo/go/golint/var-decl.go /^var out2 io.Writer = newWriter() \/\/ MATCH \/should omit.*io\\.Writer\/$/;" v parenID tenets/codelingo/go/golint/var-decl.go /^var parenID int64 = (17)$/;" v parse flows/codelingo/docs/docs/parse/parse.go /^package parse$/;" p +parseArgsTest sdk/flow/cli_test.go /^var parseArgsTest = []struct {$/;" v parseBitBucketPR flows/codelingo/review/review/reviewpr.go /^func parseBitBucketPR(urlStr []string) (*PROpts, error) {$/;" f parseGithubPR flows/codelingo/review/review/reviewpr.go /^func parseGithubPR(urlPath []string) (*PROpts, error) {$/;" f parseGitlabPR flows/codelingo/review/review/reviewpr.go /^func parseGitlabPR(urlStr []string) (*PROpts, error) {$/;" f parseTenetsDir util/mdgen/cmd/genTenets.go /^func parseTenetsDir(dirPath string) (dataStruct.HubTenets, error) {$/;" f +partitionedFile flows/codelingo/rewrite/rewrite/writer.go /^type partitionedFile struct {$/;" t perim tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^func (c circle) perim() float64 {$/;" f perim tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^func (r rect) perim() float64 {$/;" f pkg tenets/codelingo/go/golint/4.go /^package pkg$/;" p @@ -751,7 +882,7 @@ pkg tenets/codelingo/go/golint/inc.go /^package pkg$/;" p pkg tenets/codelingo/go/golint/sort.go /^package pkg$/;" p pkg_with_underscores tenets/codelingo/go/golint/names.go /^package pkg_with_underscores \/\/ MATCH \/underscore.*package name\/$/;" p position flows/codelingo/rewrite/rewrite/option/option.go /^func (o option) position() option {$/;" f -print tenets/codelingo/k8/looped-vars-outside-loop/example.go /^func print(pi *int) { fmt.Println(*pi) }$/;" f +print tenets/codelingo/landmines/looped-vars-outside-loop/example.go /^func print(pi *int) { fmt.Println(*pi) }$/;" f printHelp sdk/flow/help.go /^func printHelp(out io.Writer, templ string, data interface{}) {$/;" f priorities util/mdgen/cmd/listTenets.go /^var priorities = map[string]int{$/;" v priorityByOwner util/mdgen/cmd/listTenets.go /^func priorityByOwner(owner string) int {$/;" f @@ -761,11 +892,13 @@ products tenets/codelingo/php/sql-concats/example.php /^ $products = ExecuteSQL( pullRequestCmd flows/codelingo/pull-request/main.go /^var pullRequestCmd = cli.Command{$/;" v q tenets/codelingo/go/golint/var-decl.go /^type q int$/;" t query tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ $this->query = new Query();$/;" v -queueChange tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) queueChange(change Change) {$/;" f -queueChange tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) queueChange(change Change) {$/;" f +queueChange tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) queueChange(change Change) {$/;" f +queueChange tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) queueChange(change Change) {$/;" f +quux tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func quux() {}$/;" f +qux tenets/codelingo/effective-go/comment-first-word-as-subject/example.go /^func qux() {}$/;" f rawLingoBundle flows/codelingo/docs/docs/parse/parse.go /^type rawLingoBundle struct {$/;" t -receiveEvent tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) receiveEvent(topic string, data interface{}) {$/;" f -receiveEvent tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) receiveEvent(topic string, data interface{}) {$/;" f +receiveEvent tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) receiveEvent(topic string, data interface{}) {$/;" f +receiveEvent tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) receiveEvent(topic string, data interface{}) {$/;" f rect tenets/codelingo/jenkinsx/orginterfaces/bad-interface.go /^type rect struct {$/;" t redirect tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ public function redirect($url) {$/;" f render flows/codelingo/docs/render/terminal.go /^package render$/;" p @@ -774,21 +907,23 @@ req tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^$req = ne requestBundle flows/codelingo/docs/docs/parse/parse.go /^func requestBundle(owner, bundleName string) (map[string]string, error) {$/;" f requestDotLingo flows/codelingo/docs/docs/parse/parse.go /^func requestDotLingo(owner, bundle, name string) (string, error) {$/;" f resolveImports flows/codelingo/docs/docs/parse/parse.go /^func resolveImports(parentQuery *dotlingo.Dotlingo) ([]*dotlingo.Dotlingo, error) {$/;" f -returnCallAndNil tenets/codelingo/go/nil_only_functions/example.go /^func returnCallAndNil() (int, error) {$/;" f -returnMultipleMixed tenets/codelingo/go/nil_only_functions/example.go /^func returnMultipleMixed() (*example, error) {$/;" f -returnMultipleOnlyNil tenets/codelingo/go/nil_only_functions/example.go /^func returnMultipleOnlyNil() (*example, error) {$/;" f -returnNilHasComment tenets/codelingo/go/nil_only_functions/example.go /^func returnNilHasComment() error {$/;" f -returnNonNil tenets/codelingo/go/nil_only_functions/example.go /^func returnNonNil() (int, error) {$/;" f -returnNum tenets/codelingo/go/nil_only_functions/example.go /^func returnNum() int {$/;" f -returnPointerAndNil tenets/codelingo/go/nil_only_functions/example.go /^func returnPointerAndNil() (*example, error) {$/;" f -returnsMixed tenets/codelingo/go/nil_only_functions/example.go /^func returnsMixed() error {$/;" f +returnCallAndNil tenets/codelingo/go/nil-only-functions/example.go /^func returnCallAndNil() (int, error) {$/;" f +returnMultipleMixed tenets/codelingo/go/nil-only-functions/example.go /^func returnMultipleMixed() (*example, error) {$/;" f +returnMultipleOnlyNil tenets/codelingo/go/nil-only-functions/example.go /^func returnMultipleOnlyNil() (*example, error) {$/;" f +returnNilHasComment tenets/codelingo/go/nil-only-functions/example.go /^func returnNilHasComment() error {$/;" f +returnNonNil tenets/codelingo/go/nil-only-functions/example.go /^func returnNonNil() (int, error) {$/;" f +returnNum tenets/codelingo/go/nil-only-functions/example.go /^func returnNum() int {$/;" f +returnPointerAndNil tenets/codelingo/go/nil-only-functions/example.go /^func returnPointerAndNil() (*example, error) {$/;" f +returnsMixed tenets/codelingo/go/nil-only-functions/example.go /^func returnsMixed() error {$/;" f +returnsNil tenets/codelingo/landmines/misused-nil-interface/example.go /^func returnsNil() error {$/;" f +returnsTrue tenets/codelingo/go/bool-param/example.go /^func returnsTrue() bool {$/;" f review flows/codelingo/review/review/checksyntax.go /^package review$/;" p review flows/codelingo/review/review/cli.go /^package review$/;" p review flows/codelingo/review/review/decorator.go /^package review$/;" p review flows/codelingo/review/review/review.go /^package review$/;" p review flows/codelingo/review/review/review_test.go /^package review$/;" p review flows/codelingo/review/review/reviewpr.go /^package review$/;" p -reviewAction flows/codelingo/review/review/cli.go /^func reviewAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), error) {$/;" f +reviewAction flows/codelingo/review/review/cli.go /^func reviewAction(cliCtx *cli.Context) (chan proto.Message, <-chan *flowutil.UserVar, chan error, func(), error) {$/;" f reviewPullRequestAction flows/codelingo/pull-request/main.go /^func reviewPullRequestAction(ctx *cli.Context) {$/;" f reviewPullRequestCMD flows/codelingo/pull-request/main.go /^func reviewPullRequestCMD(cliCtx *cli.Context) (string, error) {$/;" f reviewRequire flows/codelingo/review/review/cli.go /^func reviewRequire() error {$/;" f @@ -798,25 +933,34 @@ rewrite flows/codelingo/rewrite/rewrite/decorator.go /^package rewrite$/;" p rewrite flows/codelingo/rewrite/rewrite/package_test.go /^package rewrite$/;" p rewrite flows/codelingo/rewrite/rewrite/writer.go /^package rewrite$/;" p rewrite flows/codelingo/rewrite/rewrite/writer_test.go /^package rewrite$/;" p -rewriteAction flows/codelingo/rewrite/rewrite/cli.go /^func rewriteAction(cliCtx *cli.Context) (chan proto.Message, chan error, func(), error) {$/;" f +rewriteAction flows/codelingo/rewrite/rewrite/cli.go /^func rewriteAction(cliCtx *cli.Context) (chan proto.Message, <-chan *flowutil.UserVar, chan error, func(), error) {$/;" f +rewriteFile flows/codelingo/rewrite/rewrite/writer.go /^func rewriteFile(ctx *cli.Context, inputSRC, newSRC []byte, parts partitionedFile, hunk *rewriterpc.Hunk) ([]byte, error) {$/;" f rewriteRequire flows/codelingo/rewrite/rewrite/cli.go /^func rewriteRequire() error {$/;" f rootCmd util/mdgen/cmd/root.go /^var rootCmd = &cobra.Command{$/;" v rpc flows/codelingo/rewrite/rpc/rewrite.pb.go /^package rpc$/;" p rpcTimeoutMsec tenets/codelingo/go/golint/time.go /^var rpcTimeoutMsec = flag.Duration("rpc_timeout", 100*time.Millisecond, "some flag") \/\/ MATCH \/Msec.*\\*time.Duration\/$/;" v +safe tenets/codelingo/go/unsafe-go-routine-variables/test.go /^func safe() <-chan int {$/;" f searchAction flows/codelingo/search/main.go /^func searchAction(ctx *cli.Context) {$/;" f searchCMD flows/codelingo/search/main.go /^func searchCMD(cliCtx *cli.Context) (string, error) {$/;" f searchCommand flows/codelingo/search/main.go /^var searchCommand = cli.Command{$/;" v searchRequire flows/codelingo/search/main.go /^func searchRequire() error {$/;" f -sendReq tenets/codelingo/go/reallocated_slice/broken.go /^func (w *HubWatcher) sendReq(req interface{}) {$/;" f -sendReq tenets/codelingo/go/reallocated_slice/fixed.go /^func (w *HubWatcher) sendReq(req interface{}) {$/;" f +sendReq tenets/codelingo/go/reallocated-slice/broken.go /^func (w *HubWatcher) sendReq(req interface{}) {$/;" f +sendReq tenets/codelingo/go/reallocated-slice/fixed.go /^func (w *HubWatcher) sendReq(req interface{}) {$/;" f serve tenets/codelingo/go/todo/example.go /^func serve() error {$/;" f serverImpl tenets/codelingo/go/golint/var-decl.go /^type serverImpl struct{}$/;" t +set sdk/flow/user_variable.go /^func (s *UserVar) set(val string) {$/;" f setBaseApp sdk/flow/cli.go /^func setBaseApp(cliApp *CLIApp) {$/;" f setHelp sdk/flow/cli.go /^func (f *flowRunner) setHelp() {$/;" f -somethingAction tenets/modica/default/transport/made_up/something.php /^ public function somethingAction(){} \/\/ Fits Template$/;" f -somethingAction tenets/modica/default/transport/wrong_controller_name/something.php /^ public function somethingAction(){} \/\/ ISSUE$/;" f -somethingAction tenets/modica/default/transport/wrong_filename/asdf.php /^ public function somethingAction(){} \/\/ ISSUE$/;" f -somethingAction tenets/modica/default/transport/wrong_namespace/something.php /^ public function somethingAction(){} \/\/ Issue$/;" f +someFuncA tenets/codelingo/go/bool-param/example.go /^func someFuncA(a string, b bool) {}$/;" f +someFuncB tenets/codelingo/go/bool-param/example.go /^func someFuncB(b bool, a string) {}$/;" f +someFuncC tenets/codelingo/go/bool-param/example.go /^func someFuncC(a string, b bool, c string) {}$/;" f +someFuncD tenets/codelingo/go/bool-param/example.go /^func someFuncD(b bool, c string, d bool) {}$/;" f +somepackage tenets/codelingo/effective-go/package-comment/example.go /^package somepackage$/;" p +somethingAction tenets/codelingo/modica/transport/made_up/something.php /^ public function somethingAction(){} \/\/ Fits Template$/;" f +somethingAction tenets/codelingo/modica/transport/wrong_controller_name/something.php /^ public function somethingAction(){} \/\/ ISSUE$/;" f +somethingAction tenets/codelingo/modica/transport/wrong_filename/asdf.php /^ public function somethingAction(){} \/\/ ISSUE$/;" f +somethingAction tenets/codelingo/modica/transport/wrong_namespace/something.php /^ public function somethingAction(){} \/\/ Issue$/;" f +splitSRC flows/codelingo/rewrite/rewrite/writer.go /^func splitSRC(hunk *rewriterpc.Hunk, fileSRC []byte) partitionedFile {$/;" f sql tenets/codelingo/php/sql-concats/example.php /^ $sql = "SELECT cost FROM ";$/;" v srcFileTemplate tenets/codelingo/jenkinsx/intformat/generate_tests.go /^var srcFileTemplate = `$/;" v srccode tenets/codelingo/jenkinsx/intformat/generate_tests.go /^type srccode struct {$/;" t @@ -827,27 +971,31 @@ string tenets/codelingo/php/eval-expression/test.php /^$string = 'cup';$/;" v stringT tenets/codelingo/go/golint/var-decl.go /^type stringT string$/;" t stringV tenets/codelingo/go/golint/var-decl.go /^var stringV stringT = "abc"$/;" v subOneInLoop tenets/codelingo/go/golint/inc.go /^func subOneInLoop(y int) {$/;" f -sync tenets/codelingo/k8/well-named-interface/sync/example.go /^package sync$/;" p -syncOp tenets/codelingo/k8/well-named-interface/sync/example.go /^type syncOp interface {$/;" t +sync tenets/codelingo/kubernetes/well-named-interface/sync/example.go /^package sync$/;" p +syncOp tenets/codelingo/kubernetes/well-named-interface/sync/example.go /^type syncOp interface {$/;" t t tenets/codelingo/go/golint/names.go /^type t struct{}$/;" t t_wow tenets/codelingo/go/golint/names.go /^type t_wow struct { \/\/ MATCH \/underscore.*type.*t_wow\/$/;" t takes_bool tenets/codelingo/php/boolean-argument/test.php /^function takes_bool(bool $input) \/\/ ISSUE$/;" f tenetURL flows/codelingo/docs/docs/parse/parse.go /^const tenetURL = "https:\/\/raw.githubusercontent.com\/codelingo\/codelingo\/master\/tenets\/%s\/%s\/%s\/codelingo.yaml"$/;" c tenetsToRawMdDocs flows/codelingo/docs/main.go /^func tenetsToRawMdDocs(docs []map[string]string, templateSRC string) (string, error) {$/;" f test flows/codelingo/rewrite/rewrite/test/mock.go /^package test$/;" p -test tenets/codelingo/k8/new-package-requires-test/test/testing-good.go /^package test$/;" p +test tenets/codelingo/kubernetes/new-package-requires-test/test/testing-good.go /^package test$/;" p testData flows/codelingo/rewrite/rewrite/writer_test.go /^var testData = []struct {$/;" v testFileTemplate tenets/codelingo/jenkinsx/intformat/generate_tests.go /^var testFileTemplate = `$/;" v +testFunc tenets/codelingo/landmines/shadowed-func-parameter/example.go /^func testFunc() (err string) {$/;" f +testStruct2 tenets/codelingo/kubernetes/well-named-lock/example.go /^type testStruct2 struct {$/;" t testdata tenets/codelingo/go/golint/pkg-doc2.go /^package testdata$/;" p +testpackage tenets/codelingo/lightning-network-daemon/exported-package-is-tested/test/my_test.go /^package testpackage$/;" p theVar tenets/codelingo/go/init/example.go /^var theVar = true$/;" v thing tenets/codelingo/php/if-assignment/test.php /^$thing = "ting";$/;" v +thing tenets/codelingo/psr2/elseif-not-else-if/example.php /^ $thing = 2;$/;" v timeoutSecs tenets/codelingo/go/golint/time.go /^var timeoutSecs = 5 * time.Second \/\/ MATCH \/Secs.*time.Duration\/$/;" v toggleOption flows/codelingo/rewrite/rewrite/option/option.go /^func (o *option) toggleOption(opt option) {$/;" f tparallel tenets/codelingo/jenkinsx/tparallel/auth.go /^package tparallel$/;" p tparallel_test tenets/codelingo/jenkinsx/tparallel/auth_integration_test.go /^package tparallel_test$/;" p -tryTheThing tenets/codelingo/k8/shadowed-func-parameter/example.go /^func tryTheThing() (string, error) {$/;" f unexp tenets/codelingo/go/golint/errors.go /^var unexp = errors.New("some unexported error") \/\/ MATCH \/error var.*unexp.*errFoo\/$/;" v unexp tenets/codelingo/go/golint/unexp-return.go /^func unexp() hidden { \/\/ ok$/;" f +unsafe tenets/codelingo/go/unsafe-go-routine-variables/test.go /^func unsafe() <-chan int { \/\/ ISSUE$/;" f url tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ $url = "\/";$/;" v url tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ $url = $request->query->get("url", '');$/;" v urlParts tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ $urlParts = parse_url($url);$/;" v @@ -865,15 +1013,17 @@ vcsGit flows/codelingo/rewrite/rewrite/cli.go /^ vcsGit string = "git"$/;" c vcsP4 flows/codelingo/review/review/cli.go /^ vcsP4 string = "perforce"$/;" c vcsP4 flows/codelingo/rewrite/rewrite/cli.go /^ vcsP4 string = "perforce"$/;" c viewAction tenets/codelingo/php/offsite-redirection/UserDefinedRedirect.php /^ public function viewAction(Request $request)$/;" f -watcher tenets/codelingo/go/reallocated_slice/broken.go /^package watcher$/;" p -watcher tenets/codelingo/go/reallocated_slice/fixed.go /^package watcher$/;" p +watcher tenets/codelingo/go/reallocated-slice/broken.go /^package watcher$/;" p +watcher tenets/codelingo/go/reallocated-slice/fixed.go /^package watcher$/;" p writeCmd util/mdgen/cmd/write.go /^var writeCmd = &cobra.Command{$/;" v writeFile util/mdgen/cmd/write.go /^func writeFile(tmplPath, outPath string, data interface{}) error {$/;" f writeLexMD util/mdgen/cmd/genLexicon.go /^func writeLexMD(data *lexInfo) error {$/;" f x tenets/codelingo/go/golint/context.go /^func x(ctx context.Context) { \/\/ ok$/;" f x tenets/codelingo/go/golint/context.go /^func x(ctx context.Context, s string) { \/\/ ok$/;" f x tenets/codelingo/go/golint/var-decl.go /^var x = 0$/;" v -x tenets/west/default/allocation/variable_allocation.cpp /^ int x;$/;" m class:Obj file: +x tenets/codelingo/west/allocation/variable_allocation.cpp /^ int x;$/;" m class:Obj file: +xxx_messageInfo_HelloReply tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var xxx_messageInfo_HelloReply proto.InternalMessageInfo$/;" v +xxx_messageInfo_HelloRequest tenets/codelingo/effective-go/underscores-in-name/proto.pb.go /^var xxx_messageInfo_HelloRequest proto.InternalMessageInfo$/;" v y tenets/codelingo/go/golint/context.go /^func y(s string, ctx context.Context) { \/\/ MATCH \/context.Context should be the first parameter.*\/$/;" f y tenets/codelingo/go/golint/context.go /^func y(s string, r int, ctx context.Context, x int) { \/\/ MATCH \/context.Context should be the first parameter.*\/$/;" f y tenets/codelingo/go/golint/var-decl.go /^var y string = q(1).String() \/\/ MATCH \/should.*string\/$/;" v diff --git a/tenets.yaml b/tenets.yaml new file mode 100644 index 00000000..e69de29b diff --git a/tenets/codelingo/cacophony/caught-generic-exceptions/codelingo.yaml b/tenets/codelingo/cacophony/caught-generic-exceptions/codelingo.yaml index 369e9598..eb5f9191 100644 --- a/tenets/codelingo/cacophony/caught-generic-exceptions/codelingo.yaml +++ b/tenets/codelingo/cacophony/caught-generic-exceptions/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: caught-generic-exceptions - flows: + actions: codelingo/docs: title: Caught Generic Exceptions body: | diff --git a/tenets/codelingo/cacophony/debug-prints/codelingo.yaml b/tenets/codelingo/cacophony/debug-prints/codelingo.yaml index 268d0b3c..207fc6b4 100644 --- a/tenets/codelingo/cacophony/debug-prints/codelingo.yaml +++ b/tenets/codelingo/cacophony/debug-prints/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: debug-prints - flows: + actions: codelingo/docs: title: Debug Prints body: | diff --git a/tenets/codelingo/cacophony/defer-in-loop/codelingo.yaml b/tenets/codelingo/cacophony/defer-in-loop/codelingo.yaml index 2dbb2b27..ba14814b 100644 --- a/tenets/codelingo/cacophony/defer-in-loop/codelingo.yaml +++ b/tenets/codelingo/cacophony/defer-in-loop/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: defer-in-loop - flows: + actions: codelingo/docs: title: Defer In Loop body: | diff --git a/tenets/codelingo/cacophony/lingo_bundle.yaml b/tenets/codelingo/cacophony/lingo_bundle.yaml index 140c13f2..dfa5f598 100644 --- a/tenets/codelingo/cacophony/lingo_bundle.yaml +++ b/tenets/codelingo/cacophony/lingo_bundle.yaml @@ -1,14 +1,14 @@ description: Tenets written for The Cacophony Project. version: 0.0.0 tenets: - - caught-generic-exceptions - - debug-prints - - defer-in-loop - - no-fmt-print - - raise-generic-exceptions - - redundant-defer-wraps +- caught-generic-exceptions +- debug-prints +- defer-in-loop +- no-fmt-print +- raise-generic-exceptions +- redundant-defer-wraps tags: - - golang - - go - - python - - py \ No newline at end of file +- golang +- go +- python +- py diff --git a/tenets/codelingo/cacophony/no-fmt-print/codelingo.yaml b/tenets/codelingo/cacophony/no-fmt-print/codelingo.yaml index 06008bbe..7f374964 100644 --- a/tenets/codelingo/cacophony/no-fmt-print/codelingo.yaml +++ b/tenets/codelingo/cacophony/no-fmt-print/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: no-fmt-printx - flows: + actions: codelingo/docs: title: No Fmt Printx body: | diff --git a/tenets/codelingo/cacophony/raise-generic-exceptions/codelingo.yaml b/tenets/codelingo/cacophony/raise-generic-exceptions/codelingo.yaml index e6355267..367ec125 100644 --- a/tenets/codelingo/cacophony/raise-generic-exceptions/codelingo.yaml +++ b/tenets/codelingo/cacophony/raise-generic-exceptions/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: raised-generic-exceptions - flows: + actions: codelingo/docs: title: Raised Generic Exceptions body: | diff --git a/tenets/codelingo/cacophony/redundant-defer-wraps/codelingo.yaml b/tenets/codelingo/cacophony/redundant-defer-wraps/codelingo.yaml index a0a1801c..2cb401d3 100644 --- a/tenets/codelingo/cacophony/redundant-defer-wraps/codelingo.yaml +++ b/tenets/codelingo/cacophony/redundant-defer-wraps/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: redundant-defer-wraps - flows: + actions: codelingo/docs: title: Redundant Defer Wraps body: | diff --git a/tenets/codelingo/cockroachdb/avoid-bool-params/codelingo.yaml b/tenets/codelingo/cockroachdb/avoid-bool-params/codelingo.yaml index 0e2d30c1..c2a4eb9b 100644 --- a/tenets/codelingo/cockroachdb/avoid-bool-params/codelingo.yaml +++ b/tenets/codelingo/cockroachdb/avoid-bool-params/codelingo.yaml @@ -1,9 +1,10 @@ tenets: + - name: avoid-bool-params - import: codelingo/go/bool-param # - # Uncomment this section when we are able to override flows + # Uncomment this section when we are able to override actions # - # flows: + # actions: # codelingo/docs: # title: Avoid Bool Parameters # body: | @@ -22,4 +23,4 @@ tenets: # In cases where the bool in question, along with other arguments, acts as a "knob" to the function consider # replacing it with some type of "configuration" struct (for examples, see Dave Cheney's treatment of the topic). # In situations where there's a single bool param or the situation is less clear-cut, consider replacing the - # bool in question with an enum. \ No newline at end of file + # bool in question with an enum. diff --git a/tenets/codelingo/cockroachdb/fmt-verbs/codelingo.yaml b/tenets/codelingo/cockroachdb/fmt-verbs/codelingo.yaml index 518e88aa..18c2d872 100644 --- a/tenets/codelingo/cockroachdb/fmt-verbs/codelingo.yaml +++ b/tenets/codelingo/cockroachdb/fmt-verbs/codelingo.yaml @@ -5,6 +5,7 @@ funcs: function (str, order) { order-- var matches = str.match(/%[A-Za-z]/g) + if (matches == null) return false if (matches.length <= order) return false var result = matches[order] return result === "%v" @@ -43,7 +44,7 @@ tenets: - name: fmt-verbs vars: result: "{{verbForType(argType)}}" - flows: + actions: codelingo/docs: title: Use Appropriate Verbs in Format body: | diff --git a/tenets/codelingo/cockroachdb/func-args-inline-comments/codelingo.yaml b/tenets/codelingo/cockroachdb/func-args-inline-comments/codelingo.yaml index 79b13a3f..11320b2c 100644 --- a/tenets/codelingo/cockroachdb/func-args-inline-comments/codelingo.yaml +++ b/tenets/codelingo/cockroachdb/func-args-inline-comments/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: func-args-inline-comments - flows: + actions: codelingo/docs: title: Function Arguments Inline Comments body: | diff --git a/tenets/codelingo/cockroachdb/line-length-limit/codelingo.yaml b/tenets/codelingo/cockroachdb/line-length-limit/codelingo.yaml index 164313a8..459a9b42 100644 --- a/tenets/codelingo/cockroachdb/line-length-limit/codelingo.yaml +++ b/tenets/codelingo/cockroachdb/line-length-limit/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: line-length-limit - flows: + actions: codelingo/docs: title: Wrap Long Lines body: | diff --git a/tenets/codelingo/cockroachdb/lingo_bundle.yaml b/tenets/codelingo/cockroachdb/lingo_bundle.yaml index cc66bd0b..939caad4 100644 --- a/tenets/codelingo/cockroachdb/lingo_bundle.yaml +++ b/tenets/codelingo/cockroachdb/lingo_bundle.yaml @@ -1,13 +1,13 @@ description: Best Practices for CockroachDB from their contributor docs. version: 0.0.0 tenets: - - avoid-bool-params - - fmt-verbs - - func-args-inline-comments - - line-length-limit - - wrapping-func-signatures +- avoid-bool-params +- fmt-verbs +- func-args-inline-comments +- line-length-limit +- wrapping-func-signatures tags: - - golang - - go - - cockroach - - cockroachdb +- golang +- go +- cockroach +- cockroachdb diff --git a/tenets/codelingo/cockroachdb/wrapping-func-signatures/codelingo.yaml b/tenets/codelingo/cockroachdb/wrapping-func-signatures/codelingo.yaml index 5f1c6002..3c73640c 100644 --- a/tenets/codelingo/cockroachdb/wrapping-func-signatures/codelingo.yaml +++ b/tenets/codelingo/cockroachdb/wrapping-func-signatures/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: wrapping-func-signatures - flows: + actions: codelingo/docs: title: Wrapping Function Signatures body: | diff --git a/tenets/codelingo/code-review-comments/FULL_TEXT.md b/tenets/codelingo/code-review-comments/FULL_TEXT.md new file mode 100644 index 00000000..ecae654b --- /dev/null +++ b/tenets/codelingo/code-review-comments/FULL_TEXT.md @@ -0,0 +1,611 @@ +# Go Code Review Comments + +This page collects common comments made during reviews of Go code, so +that a single detailed explanation can be referred to by shorthands. +This is a laundry list of common mistakes, not a comprehensive style guide. + +You can view this as a supplement to [Effective Go](https://golang.org/doc/effective_go.html). + +**Please [discuss changes](https://golang.org/issue/new?title=wiki%3A+CodeReviewComments+change&body=&labels=Documentation) before editing this page**, even _minor_ ones. Many people have opinions and this is not the place for edit wars. + +* [Gofmt](#gofmt) +* [Comment Sentences](#comment-sentences) +* [Contexts](#contexts) +* [Copying](#copying) +* [Crypto Rand](#crypto-rand) +* [Declaring Empty Slices](#declaring-empty-slices) +* [Doc Comments](#doc-comments) +* [Don't Panic](#dont-panic) +* [Error Strings](#error-strings) +* [Examples](#examples) +* [Goroutine Lifetimes](#goroutine-lifetimes) +* [Handle Errors](#handle-errors) +* [Imports](#imports) +* [Import Dot](#import-dot) +* [In-Band Errors](#in-band-errors) +* [Indent Error Flow](#indent-error-flow) +* [Initialisms](#initialisms) +* [Interfaces](#interfaces) +* [Line Length](#line-length) +* [Mixed Caps](#mixed-caps) +* [Named Result Parameters](#named-result-parameters) +* [Naked Returns](#naked-returns) +* [Package Comments](#package-comments) +* [Package Names](#package-names) +* [Pass Values](#pass-values) +* [Receiver Names](#receiver-names) +* [Receiver Type](#receiver-type) +* [Synchronous Functions](#synchronous-functions) +* [Useful Test Failures](#useful-test-failures) +* [Variable Names](#variable-names) + +## Gofmt + +Run [gofmt](https://golang.org/cmd/gofmt/) on your code to automatically fix the majority of mechanical style issues. Almost all Go code in the wild uses `gofmt`. The rest of this document addresses non-mechanical style points. + +An alternative is to use [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports), a superset of `gofmt` which additionally adds (and removes) import lines as necessary. + +## Comment Sentences + +See https://golang.org/doc/effective_go.html#commentary. Comments documenting declarations should be full sentences, even if that seems a little redundant. This approach makes them format well when extracted into godoc documentation. Comments should begin with the name of the thing being described and end in a period: + +```go +// Request represents a request to run a command. +type Request struct { ... + +// Encode writes the JSON encoding of req to w. +func Encode(w io.Writer, req *Request) { ... +``` + +and so on. + +## Contexts + +Values of the context.Context type carry security credentials, +tracing information, deadlines, and cancellation signals across API +and process boundaries. Go programs pass Contexts explicitly along +the entire function call chain from incoming RPCs and HTTP requests +to outgoing requests. + +Most functions that use a Context should accept it as their first parameter: + +``` +func F(ctx context.Context, /* other arguments */) {} +``` + +[Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/context-first-arg) + +A function that is never request-specific may use context.Background(), +but err on the side of passing a Context even if you think you don't need +to. The default case is to pass a Context; only use context.Background() +directly if you have a good reason why the alternative is a mistake. + +Don't add a Context member to a struct type; instead add a ctx parameter +to each method on that type that needs to pass it along. The one exception +is for methods whose signature must match an interface in the standard library +or in a third party library. [Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/context-in-struct) + +Don't create custom Context types or use interfaces other than Context in +function signatures. [Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/no-custom-context) + +If you have application data to pass around, put it in a parameter, +in the receiver, in globals, or, if it truly belongs there, in a Context value. + +Contexts are immutable, so it's fine to pass the same ctx to multiple +calls that share the same deadline, cancellation signal, credentials, +parent trace, etc. + +## Copying + +To avoid unexpected aliasing, be careful when copying a struct from another package. +For example, the bytes.Buffer type contains a `[]byte` slice and, as an optimization +for small strings, a small byte array to which the slice may refer. If you copy a `Buffer`, +the slice in the copy may alias the array in the original, causing subsequent method +calls to have surprising effects. + +In general, do not copy a value of type `T` if its methods are associated with the +pointer type, `*T`. + +## Declaring Empty Slices + +When declaring an empty slice, prefer + +```go +var t []string +``` + +over + +```go +t := []string{} +``` + +The former declares a nil slice value, while the latter is non-nil but zero-length. They are functionally equivalent—their `len` and `cap` are both zero—but the nil slice is the preferred style. + +Note that there are limited circumstances where a non-nil but zero-length slice is preferred, such as when encoding JSON objects (a `nil` slice encodes to `null`, while `[]string{}` encodes to the JSON array `[]`). + +When designing interfaces, avoid making a distinction between a nil slice and a non-nil, zero-length slice, as this can lead to subtle programming errors. + +For more discussion about nil in Go see Francesc Campoy's talk [Understanding Nil](https://www.youtube.com/watch?v=ynoY2xz-F8s). + +[Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/declare-empty-slice) + +## Crypto Rand + +Do not use package `math/rand` to generate keys, even throwaway ones. +Unseeded, the generator is completely predictable. Seeded with `time.Nanoseconds()`, +there are just a few bits of entropy. Instead, use `crypto/rand`'s Reader [(Tenet)](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/use-crypto-rand), +and if you need text, print to hexadecimal or base64: + +``` go +import ( + "crypto/rand" + // "encoding/base64" + // "encoding/hex" + "fmt" +) + +func Key() string { + buf := make([]byte, 16) + _, err := rand.Read(buf) + if err != nil { + panic(err) // out of randomness, should never happen + } + return fmt.Sprintf("%x", buf) + // or hex.EncodeToString(buf) + // or base64.StdEncoding.EncodeToString(buf) +} +``` + +## Doc Comments + +All top-level, exported names should have doc comments, as should non-trivial unexported type or function declarations. See https://golang.org/doc/effective_go.html#commentary for more information about commentary conventions. + +## Don't Panic + +See https://golang.org/doc/effective_go.html#errors. Don't use panic for normal error handling. Use error and multiple return values. + +## Error Strings + +Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context. That is, use `fmt.Errorf("something bad")` not `fmt.Errorf("Something bad")`, so that `log.Printf("Reading %s: %v", filename, err)` formats without a spurious capital letter mid-message. This does not apply to logging, which is implicitly line-oriented and not combined inside other messages. [Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/go-error-fmt) + +## Examples + +When adding a new package, include examples of intended usage: a runnable Example, +or a simple test demonstrating a complete call sequence. + +Read more about [testable Example() functions](https://blog.golang.org/examples). + +## Goroutine Lifetimes + +When you spawn goroutines, make it clear when - or whether - they exit. + +Goroutines can leak by blocking on channel sends or receives: the garbage collector +will not terminate a goroutine even if the channels it is blocked on are unreachable. + +Even when goroutines do not leak, leaving them in-flight when they are no longer +needed can cause other subtle and hard-to-diagnose problems. Sends on closed channels +panic. Modifying still-in-use inputs "after the result isn't needed" can still lead +to data races. And leaving goroutines in-flight for arbitrarily long can lead to +unpredictable memory usage. + +Try to keep concurrent code simple enough that goroutine lifetimes are obvious. +If that just isn't feasible, document when and why the goroutines exit. + +## Handle Errors + +See https://golang.org/doc/effective_go.html#errors. Do not discard errors using `_` variables [(Tenet)](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/do-not-discard-errors). If a function returns an error, check it to make sure the function succeeded. Handle the error, return it, or, in truly exceptional situations, panic. [Tenet](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/return-discarded-errors/codelingo.yaml) + +## Imports + +Avoid renaming imports except to avoid a name collision; good package names +should not require renaming. In the event of collision, prefer to rename the most +local or project-specific import. [Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/avoid-renaming-imports) + + +Imports are organized in groups, with blank lines between them. +The standard library packages are always in the first group. + +```go +package main + +import ( + "fmt" + "hash/adler32" + "os" + + "appengine/foo" + "appengine/user" + + "github.com/foo/bar" + "rsc.io/goversion/version" +) +``` + +goimports will do this for you. + +## ImportBlank + +Packages that are imported only for their side effects (using the syntax `import +_ "pkg"`) should only be imported in the main package of a program, or in tests +that require them. + +## Import Dot + +The import . form can be useful in tests that, due to circular dependencies, cannot be made part of the package being tested: + +```go +package foo_test + +import ( + "bar/testutil" // also imports "foo" + . "foo" +) +``` + +In this case, the test file cannot be in package foo because it uses bar/testutil, which imports foo. So we use the 'import .' form to let the file pretend to be part of package foo even though it is not. Except for this one case, do not use import . in your programs. It makes the programs much harder to read because it is unclear whether a name like Quux is a top-level identifier in the current package or in an imported package. + +## In-Band Errors + +In C and similar languages, it's common for functions to return values like -1 +or null to signal errors or missing results: + +```go +// Lookup returns the value for key or "" if there is no mapping for key. +func Lookup(key string) string + +// Failing to check a for an in-band error value can lead to bugs: +Parse(Lookup(key)) // returns "parse failure for value" instead of "no value for key" +``` + +Go's support for multiple return values provides a better solution. +Instead of requiring clients to check for an in-band error value, a function should return +an additional value to indicate whether its other return values are valid. This return +value may be an error, or a boolean when no explanation is needed. +It should be the final return value. + +``` go +// Lookup returns the value for key or ok=false if there is no mapping for key. +func Lookup(key string) (value string, ok bool) +``` + +This prevents the caller from using the result incorrectly: + +``` go +Parse(Lookup(key)) // compile-time error +``` + +And encourages more robust and readable code: + +``` go +value, ok := Lookup(key) +if !ok { + return fmt.Errorf("no value for %q", key) +} +return Parse(value) +``` + +This rule applies to exported functions but is also useful +for unexported functions. + +Return values like nil, "", 0, and -1 are fine when they are +valid results for a function, that is, when the caller need not +handle them differently from other values. + +Some standard library functions, like those in package "strings", +return in-band error values. This greatly simplifies string-manipulation +code at the cost of requiring more diligence from the programmer. +In general, Go code should return additional values for errors. + +## Indent Error Flow + +Try to keep the normal code path at a minimal indentation, and indent the error handling, dealing with it first. This improves the readability of the code by permitting visually scanning the normal path quickly. For instance, don't write: + +```go +if err != nil { + // error handling +} else { + // normal code +} +``` + +Instead, write: + +```go +if err != nil { + // error handling + return // or continue, etc. +} +// normal code +``` + +If the `if` statement has an initialization statement, such as: + +```go +if x, err := f(); err != nil { + // error handling + return +} else { + // use x +} +``` + +then this may require moving the short variable declaration to its own line: + +```go +x, err := f() +if err != nil { + // error handling + return +} +// use x +``` + +## Initialisms + +Words in names that are initialisms or acronyms (e.g. "URL" or "NATO") have a consistent case. For example, "URL" should appear as "URL" or "url" (as in "urlPony", or "URLPony"), never as "Url". As an example: ServeHTTP not ServeHttp. For identifiers with multiple initialized "words", use for example "xmlHTTPRequest" or "XMLHTTPRequest". + +This rule also applies to "ID" when it is short for "identifier" (which is pretty much all cases when it's not the "id" as in "ego", "superego"), so write "appID" instead of "appId". + +Code generated by the protocol buffer compiler is exempt from this rule. Human-written code is held to a higher standard than machine-written code. + +## Interfaces + +Go interfaces generally belong in the package that uses values of the +interface type, not the package that implements those values. The +implementing package should return concrete (usually pointer or struct) +types: that way, new methods can be added to implementations without +requiring extensive refactoring. + +Do not define interfaces on the implementor side of an API "for mocking"; +instead, design the API so that it can be tested using the public API of +the real implementation. + +Do not define interfaces before they are used: without a realistic example +of usage, it is too difficult to see whether an interface is even necessary, +let alone what methods it ought to contain. + +``` go +package consumer // consumer.go + +type Thinger interface { Thing() bool } + +func Foo(t Thinger) string { … } +``` + +``` go +package consumer // consumer_test.go + +type fakeThinger struct{ … } +func (t fakeThinger) Thing() bool { … } +… +if Foo(fakeThinger{…}) == "x" { … } +``` + +``` go +// DO NOT DO IT!!! +package producer + +type Thinger interface { Thing() bool } + +type defaultThinger struct{ … } +func (t defaultThinger) Thing() bool { … } + +func NewThinger() Thinger { return defaultThinger{ … } } +``` + +Instead return a concrete type and let the consumer mock the producer implementation. +``` go +package producer + +type Thinger struct{ … } +func (t Thinger) Thing() bool { … } + +func NewThinger() Thinger { return Thinger{ … } } +``` + + +## Line Length + +There is no rigid line length limit in Go code, but avoid uncomfortably long lines. +Similarly, don't add line breaks to keep lines short when they are more readable long--for example, +if they are repetitive. + +Most of the time when people wrap lines "unnaturally" (in the middle of function calls or +function declarations, more or less, say, though some exceptions are around), the wrapping would be +unnecessary if they had a reasonable number of parameters and reasonably short variable names. +Long lines seem to go with long names, and getting rid of the long names helps a lot. + +In other words, break lines because of the semantics of what you're writing (as a general rule) +and not because of the length of the line. If you find that this produces lines that are too long, +then change the names or the semantics and you'll probably get a good result. + +This is, actually, exactly the same advice about how long a function should be. There's no rule +"never have a function more than N lines long", but there is definitely such a thing as too long +of a function, and of too stuttery tiny functions, and the solution is to change where the function +boundaries are, not to start counting lines. + +## Mixed Caps + +See https://golang.org/doc/effective_go.html#mixed-caps. This applies even when it breaks conventions in other languages. For example an unexported constant is `maxLength` not `MaxLength` or `MAX_LENGTH`. [Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/camel-case-constants) + +Also see [Initialisms](https://github.com/golang/go/wiki/CodeReviewComments#initialisms). + +## Named Result Parameters + +Consider what it will look like in godoc. Named result parameters like: + +```go +func (n *Node) Parent1() (node *Node) +func (n *Node) Parent2() (node *Node, err error) +``` + +will stutter in godoc; better to use: + +```go +func (n *Node) Parent1() *Node +func (n *Node) Parent2() (*Node, error) +``` + +On the other hand, if a function returns two or three parameters of the same type, +or if the meaning of a result isn't clear from context, adding names may be useful +in some contexts. Don't name result parameters just to avoid declaring a var inside +the function; that trades off a minor implementation brevity at the cost of +unnecessary API verbosity. + + +```go +func (f *Foo) Location() (float64, float64, error) +``` + +is less clear than: + +```go +// Location returns f's latitude and longitude. +// Negative values mean south and west, respectively. +func (f *Foo) Location() (lat, long float64, err error) +``` + +Naked returns are okay if the function is a handful of lines. Once it's a medium +sized function, be explicit with your return values. Corollary: it's not worth it +to name result parameters just because it enables you to use naked returns. +Clarity of docs is always more important than saving a line or two in your function. + +Finally, in some cases you need to name a result parameter in order to change +it in a deferred closure. That is always OK. + + +## Naked Returns + +See [Named Result Parameters](#named-result-parameters). + +## Package Comments + +Package comments, like all comments to be presented by godoc, must appear adjacent to the package clause, with no blank line. + +```go +// Package math provides basic constants and mathematical functions. +package math +``` + +```go +/* +Package template implements data-driven templates for generating textual +output such as HTML. +.... +*/ +package template +``` + +For "package main" comments, other styles of comment are fine after the binary name (and it may be capitalized if it comes first), For example, for a `package main` in the directory `seedgen` you could write: + +``` go +// Binary seedgen ... +package main +``` +or +```go +// Command seedgen ... +package main +``` +or +```go +// Program seedgen ... +package main +``` +or +```go +// The seedgen command ... +package main +``` +or +```go +// The seedgen program ... +package main +``` +or +```go +// Seedgen .. +package main +``` + +These are examples, and sensible variants of these are acceptable. + +Note that starting the sentence with a lower-case word is not among the +acceptable options for package comments, as these are publicly-visible and +should be written in proper English, including capitalizing the first word +of the sentence. When the binary name is the first word, capitalizing it is +required even though it does not strictly match the spelling of the +command-line invocation. + +See https://golang.org/doc/effective_go.html#commentary for more information about commentary conventions. + +## Package Names + +All references to names in your package will be done using the package name, +so you can omit that name from the identifiers. For example, if you are in package chubby, +you don't need type ChubbyFile, which clients will write as `chubby.ChubbyFile`. +Instead, name the type `File`, which clients will write as `chubby.File`. +Avoid meaningless package names like util, common, misc, api, types, and interfaces. See http://golang.org/doc/effective_go.html#package-names and +http://blog.golang.org/package-names for more. [Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/avoid-meaningless-package-names) + +## Pass Values + +Don't pass pointers as function arguments just to save a few bytes. If a function refers to its argument `x` only as `*x` throughout, then the argument shouldn't be a pointer. Common instances of this include passing a pointer to a string (`*string`) or a pointer to an interface value (`*io.Reader`). In both cases the value itself is a fixed size and can be passed directly. This advice does not apply to large structs, or even small structs that might grow. + +## Receiver Names + +The name of a method's receiver should be a reflection of its identity; often a one or two letter abbreviation of its type suffices (such as "c" or "cl" for "Client"). Don't use generic names such as "me", "this" or "self", identifiers typical of object-oriented languages that gives the method a special meaning. In Go, the receiver of a method is just another parameter and therefore, should be named accordingly. The name need not be as descriptive as that of a method argument, as its role is obvious and serves no documentary purpose. It can be very short as it will appear on almost every line of every method of the type; familiarity admits brevity. +[Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity) + +Be consistent, too: if you call the receiver "c" in one method, don't call it "cl" in another. +[Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/same-receiver-name) +## Receiver Type + +Choosing whether to use a value or pointer receiver on methods can be difficult, especially to new Go programmers. If in doubt, use a pointer, but there are times when a value receiver makes sense, usually for reasons of efficiency, such as for small unchanging structs or values of basic type. Some useful guidelines: + + * If the receiver is a map, func or chan, don't use a pointer to them. If the receiver is a slice and the method doesn't reslice or reallocate the slice, don't use a pointer to it. + * If the method needs to mutate the receiver, the receiver must be a pointer. + * If the receiver is a struct that contains a sync.Mutex or similar synchronizing field, the receiver must be a pointer to avoid copying. + * If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it's equivalent to passing all its elements as arguments to the method. If that feels too large, it's also too large for the receiver. + * Can function or methods, either concurrently or when called from this method, be mutating the receiver? A value type creates a copy of the receiver when the method is invoked, so outside updates will not be applied to this receiver. If changes must be visible in the original receiver, the receiver must be a pointer. + * If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention more clear to the reader. + * If the receiver is a small array or struct that is naturally a value type (for instance, something like the time.Time type), with no mutable fields and no pointers, or is just a simple basic type such as int or string, a value receiver makes sense. A value receiver can reduce the amount of garbage that can be generated; if a value is passed to a value method, an on-stack copy can be used instead of allocating on the heap. (The compiler tries to be smart about avoiding this allocation, but it can't always succeed.) Don't choose a value receiver type for this reason without profiling first. + * Finally, when in doubt, use a pointer receiver. + +## Synchronous Functions + +Prefer synchronous functions - functions which return their results directly or finish any callbacks or channel ops before returning - over asynchronous ones. + +Synchronous functions keep goroutines localized within a call, making it easier to reason about their lifetimes and avoid leaks and data races. They're also easier to test: the caller can pass an input and check the output without the need for polling or synchronization. + +If callers need more concurrency, they can add it easily by calling the function from a separate goroutine. But it is quite difficult - sometimes impossible - to remove unnecessary concurrency at the caller side. + +## Useful Test Failures + +Tests should fail with helpful messages saying what was wrong, with what inputs, what was actually got, and what was expected. It may be tempting to write a bunch of assertFoo helpers, but be sure your helpers produce useful error messages. Assume that the person debugging your failing test is not you, and is not your team. A typical Go test fails like: + +```go +if got != tt.want { + t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want) // or Fatalf, if test can't test anything more past this point +} +``` + +Note that the order here is actual != expected, and the message uses that order too. Some test frameworks encourage writing these backwards: 0 != x, "expected 0, got x", and so on. Go does not. + +If that seems like a lot of typing, you may want to write a [[table-driven test|TableDrivenTests]]. + +Another common technique to disambiguate failing tests when using a test helper with different input is to wrap each caller with a different TestFoo function, so the test fails with that name: + +```go +func TestSingleValue(t *testing.T) { testHelper(t, []int{80}) } +func TestNoValues(t *testing.T) { testHelper(t, []int{}) } +``` + +In any case, the onus is on you to fail with a helpful message to whoever's debugging your code in the future. + +## Variable Names + +Variable names in Go should be short rather than long. This is especially true for local variables with limited scope ([Tenet](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/short-name-limited-scope)). Prefer `c` to `lineCount`. Prefer `i` to `sliceIndex`. + +The basic rule: the further from its declaration that a name is used, the more descriptive the name must be. For a method receiver, one or two letters is sufficient. Common variables) such as loop indices [(Tenet)](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/single-letter-loop-variable) and readers [(Tenet)](https://github.com/codelingo/codelingo/tree/master/tenets/codelingo/code-review-comments/short-reader-variable) can be a single letter (`i`, `r`). More unusual things and global variables need more descriptive names. diff --git a/tenets/codelingo/code-review-comments/README.md b/tenets/codelingo/code-review-comments/README.md index 814cd717..97e8387f 100644 --- a/tenets/codelingo/code-review-comments/README.md +++ b/tenets/codelingo/code-review-comments/README.md @@ -1,3 +1,5 @@ # Code Review Comments -Useful tenets from [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) for your Go repository. \ No newline at end of file +Useful tenets from [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) for your Go repository. + +Full text of the document with links to each implemented (or yet to be implemented) tenet found in FULL_TEXT.md \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/codelingo.yaml b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/codelingo.yaml new file mode 100644 index 00000000..e907af60 --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/codelingo.yaml @@ -0,0 +1,39 @@ +funcs: + - name: isBlacklisted + type: asserter + body: | + function(name) { + var blacklist = [ + "util", + "common", + "misc", + "api", + "types", + "interfaces", + ] + + for (var i = 0; i < blacklist.length; i++) { + if (name === blacklist[i]) { + return true + } + } + + return false + } +tenets: + - name: avoid-meaningless-package-names + actions: + codelingo/review: + comment: Avoid meaningless package names like util, common, misc, api, types, and interfaces. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#package-names) + codelingo/docs: + title: Avoid Meaningless Package Names + body: | + Avoid meaningless package names like util, common, misc, api, types, and interfaces. See http://golang.org/doc/effective_go.html#package-names and http://blog.golang.org/package-names for more. + query: | + import codelingo/ast/go + + go.file(depth = any): + @review comment + go.ident: + name as packageName + isBlacklisted(packageName) diff --git a/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/common/b.go b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/common/b.go new file mode 100644 index 00000000..805d0c79 --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/common/b.go @@ -0,0 +1 @@ +package common diff --git a/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/expected.json b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/expected.json new file mode 100755 index 00000000..e461d473 --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "This is a function, but you probably already knew that.", + "Filename": "util/a.go", + "Line": 1, + "Snippet": "\npackage util\n" + }, + { + "Comment": "This is a function, but you probably already knew that.", + "Filename": "misc/c.go", + "Line": 1, + "Snippet": "\npackage misc\n" + }, + { + "Comment": "This is a function, but you probably already knew that.", + "Filename": "common/b.go", + "Line": 1, + "Snippet": "\npackage common\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/main.go b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/main.go new file mode 100644 index 00000000..06ab7d0f --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/main.go @@ -0,0 +1 @@ +package main diff --git a/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/misc/c.go b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/misc/c.go new file mode 100644 index 00000000..36775ebc --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/misc/c.go @@ -0,0 +1 @@ +package misc diff --git a/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/util/a.go b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/util/a.go new file mode 100644 index 00000000..c7d86821 --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-meaningless-package-names/util/a.go @@ -0,0 +1 @@ +package util diff --git a/tenets/codelingo/code-review-comments/avoid-renaming-imports/codelingo.yaml b/tenets/codelingo/code-review-comments/avoid-renaming-imports/codelingo.yaml index 422d6709..9b683674 100644 --- a/tenets/codelingo/code-review-comments/avoid-renaming-imports/codelingo.yaml +++ b/tenets/codelingo/code-review-comments/avoid-renaming-imports/codelingo.yaml @@ -1,15 +1,21 @@ funcs: - - name: importNamesAreDifferent + - name: importNamesEqual type: asserter body: | function(a, b) { var aSplit = a.toLowerCase().split("/") var bSplit = b.toLowerCase().split("/") - return a[a.length - 1] !== b[b.length - 1] + return a[a.length - 1] === b[b.length - 1] + } + - name: neq + type: asserter + body: | + function(a, b) { + return a !== b } tenets: - name: avoid-renaming-imports - flows: + actions: codelingo/docs: title: Avoid Renaming Imports body: | @@ -25,12 +31,17 @@ tenets: go.gen_decl(depth = any): @review comment - go.import_spec(depth = 0:2): + go.import_spec: + start_offset as soOne # stand in for UID go.ident: # Ident is the rename and absent if not named + name as importName name != "_" go.basic_lit: # Basic_lit is the "import/path" value as importOne - go.import_spec: - go.basic_lit: - value as importTwo - importNamesAreDifferent(importOne, importTwo) \ No newline at end of file + exclude: + go.import_spec: + start_offset as soTwo + neq(soOne, soTwo) + go.basic_lit: + value as importTwo + importNamesEqual(importOne, importTwo) \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/avoid-renaming-imports/example2.go b/tenets/codelingo/code-review-comments/avoid-renaming-imports/example2.go new file mode 100644 index 00000000..57cb9f94 --- /dev/null +++ b/tenets/codelingo/code-review-comments/avoid-renaming-imports/example2.go @@ -0,0 +1,12 @@ +// Package main is an example package +package main + +import ( + badfmt "fmt" + rando "math/rand" + _ "os" +) + +func main() { + badfmt.Println("Hello, playground", rando.New()) +} diff --git a/tenets/codelingo/code-review-comments/avoid-renaming-imports/expected.json b/tenets/codelingo/code-review-comments/avoid-renaming-imports/expected.json index 73ad10cf..dd4d3628 100755 --- a/tenets/codelingo/code-review-comments/avoid-renaming-imports/expected.json +++ b/tenets/codelingo/code-review-comments/avoid-renaming-imports/expected.json @@ -1,32 +1,20 @@ [ { - "Comment": "Avoid renaming imports except to avoid a name collision; good package names\nshould not require renaming. In the event of collision, prefer to rename the\nmost local or project-specific import.\n", - "Filename": "code-review-comments/avoid-renaming-imports/example.go", + "Comment": "This import is renamed to `badfmt` but this is a bad practice. \nImport without specifying a new name.\n", + "Filename": "example.go", "Line": 5, - "Snippet": "import (\n\tbadfmt \"fmt\"\n\totherrand \"math/rand\"\n \"path/to/different/rand\"\n\t_ \"os\"" + "Snippet": "\nimport (\n\tbadfmt \"fmt\"\n\totherrand \"math/rand\"\n\t_ \"os\"" }, { - "Comment": "Avoid renaming imports except to avoid a name collision; good package names\nshould not require renaming. In the event of collision, prefer to rename the\nmost local or project-specific import.\n", - "Filename": "code-review-comments/avoid-renaming-imports/example.go", - "Line": 4, - "Snippet": "\nimport (\n\tbadfmt \"fmt\"\n\totherrand \"math/rand\"\n \"path/to/different/rand\"" - }, - { - "Comment": "Avoid renaming imports except to avoid a name collision; good package names\nshould not require renaming. In the event of collision, prefer to rename the\nmost local or project-specific import.\n", - "Filename": "code-review-comments/avoid-renaming-imports/example.go", - "Line": 4, - "Snippet": "\nimport (\n\tbadfmt \"fmt\"\n\totherrand \"math/rand\"\n \"path/to/different/rand\"" - }, - { - "Comment": "Avoid renaming imports except to avoid a name collision; good package names\nshould not require renaming. In the event of collision, prefer to rename the\nmost local or project-specific import.\n", - "Filename": "code-review-comments/avoid-renaming-imports/example.go", + "Comment": "This import is renamed to `badfmt` but this is a bad practice. \nImport without specifying a new name.\n", + "Filename": "example2.go", "Line": 5, - "Snippet": "import (\n\tbadfmt \"fmt\"\n\totherrand \"math/rand\"\n \"path/to/different/rand\"\n\t_ \"os\"" + "Snippet": "\nimport (\n\tbadfmt \"fmt\"\n\trando \"math/rand\"\n\t_ \"os\"" }, { - "Comment": "Avoid renaming imports except to avoid a name collision; good package names\nshould not require renaming. In the event of collision, prefer to rename the\nmost local or project-specific import.\n", - "Filename": "code-review-comments/avoid-renaming-imports/example.go", - "Line": 4, - "Snippet": "\nimport (\n\tbadfmt \"fmt\"\n\totherrand \"math/rand\"\n \"path/to/different/rand\"" + "Comment": "This import is renamed to `rando` but this is a bad practice. \nImport without specifying a new name.\n", + "Filename": "example2.go", + "Line": 6, + "Snippet": "import (\n\tbadfmt \"fmt\"\n\trando \"math/rand\"\n\t_ \"os\"\n)" } ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/avoid-renaming-imports/issues.yaml b/tenets/codelingo/code-review-comments/avoid-renaming-imports/issues.yaml deleted file mode 100644 index 1a3f50d0..00000000 --- a/tenets/codelingo/code-review-comments/avoid-renaming-imports/issues.yaml +++ /dev/null @@ -1,2 +0,0 @@ -issues: - - https://github.com/codelingo/codelingo/issues/212 \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/camel-case-constants/codelingo.yaml b/tenets/codelingo/code-review-comments/camel-case-constants/codelingo.yaml new file mode 100644 index 00000000..17fdea40 --- /dev/null +++ b/tenets/codelingo/code-review-comments/camel-case-constants/codelingo.yaml @@ -0,0 +1,59 @@ +funcs: + - name: isNotValid + type: asserter + body: | + function(varName) { + return (varName === varName.toUpperCase() && varName.length > 4) || (varName !== "_" && varName.indexOf("_") !== -1) + } + - name: fixName + type: resolver + body: | + function(varName){ + if(varName.indexOf("_") === -1 || varName === "_"){ + if(varName === varName.toUpperCase() && varName.length > 4) // variable is ALLCAPS, should be changed to Allcaps + return varName.charAt(0).toUpperCase() + varName.toLowerCase().substring(1) + return varName // variable can be all lowercase, mixedCaps or MixedCaps or ALLCAPS with length less than 4 (we do not change acronyms of length 4 or less) + } + private = !varName.match(/^[A-Z]/) + varName = varName.toLowerCase() + pieces = varName.split("_"); + constName = [] + if(private){ + constName.push(pieces[0]) + } + else{ + constName.push(pieces[0].charAt(0).toUpperCase() + pieces[0].substring(1)) + } + for (var i = 1; i < pieces.length; i++) { + constName.push(pieces[i].charAt(0).toUpperCase() + pieces[i].substring(1)) + } + return constName.join('') + } + +tenets: + - name: camel-case-constants + actions: + codelingo/review: + comment: Define constant "{{varName}}" as "{{fixName(varName)}}". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps) + codelingo/docs: + title: Camel case constants + body: | + See https://golang.org/doc/effective_go.html#mixed-caps. This applies even when it breaks conventions in other languages. For example an unexported constant is maxLength not MaxLength or MAX_LENGTH. + codelingo/rewrite: + #Currently, rewrite is incomplete as all the references to each constant should be fixed too. + + + query: | + import codelingo/ast/go + + go.gen_decl(depth = any): + tok == "const" + go.value_spec: + + go.names: + @review comment + @rewrite --replace "{{fixName(varName)}}" + go.ident: + name as varName + isNotValid(varName) + diff --git a/tenets/codelingo/code-review-comments/camel-case-constants/expected.json b/tenets/codelingo/code-review-comments/camel-case-constants/expected.json new file mode 100644 index 00000000..bed12c85 --- /dev/null +++ b/tenets/codelingo/code-review-comments/camel-case-constants/expected.json @@ -0,0 +1,44 @@ +[ + { + "Comment": "Define constant \"PUBLIC_PI\" as \"PublicPi\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 10, + "Snippet": "const private_Pi = 3.14 //ISSUE\nconst Public_Pi = 3.14 //ISSUE\nconst PUBLIC_PI = 3.14 //ISSUE\n\nfunc main() {" + }, + { + "Comment": "Define constant \"PIVALUE\" as \"Pivalue\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 7, + "Snippet": "const Pi = 3.14\nconst PI = 3.14\nconst PIVALUE = 3.14 //ISSUE\nconst private_Pi = 3.14 //ISSUE\nconst Public_Pi = 3.14 //ISSUE" + }, + { + "Comment": "Define constant \"private_Pi\" as \"privatePi\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 8, + "Snippet": "const PI = 3.14\nconst PIVALUE = 3.14 //ISSUE\nconst private_Pi = 3.14 //ISSUE\nconst Public_Pi = 3.14 //ISSUE\nconst PUBLIC_PI = 3.14 //ISSUE" + }, + { + "Comment": "Define constant \"Public_Pi\" as \"PublicPi\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 9, + "Snippet": "const PIVALUE = 3.14 //ISSUE\nconst private_Pi = 3.14 //ISSUE\nconst Public_Pi = 3.14 //ISSUE\nconst PUBLIC_PI = 3.14 //ISSUE\n" + }, + { + "Comment": "Define constant \"private_World\" as \"privateWorld\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 19, + "Snippet": "\tfmt.Println(\"Happy\", PIVALUE, \"Day\")\n\n\tconst private_World = \"World\" //ISSUE\n\tfmt.Println(\"Hello\", private_World)\n\tfmt.Println(\"Happy\", private_Pi, \"Day\")" + }, + { + "Comment": "Define constant \"Public_World\" as \"PublicWorld\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 23, + "Snippet": "\tfmt.Println(\"Happy\", private_Pi, \"Day\")\n\n\tconst Public_World = \"World\" //ISSUE\n\tfmt.Println(\"Hello\", Public_World)\n\tfmt.Println(\"Happy\", Public_Pi, \"Day\")" + }, + { + "Comment": "Define constant \"PUBLIC_WORLD\" as \"PublicWorld\". The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword constant names. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)", + "Filename": "test.go", + "Line": 27, + "Snippet": "\tfmt.Println(\"Happy\", Public_Pi, \"Day\")\n\n\tconst PUBLIC_WORLD = \"World\" //ISSUE\n\tfmt.Println(\"Hello\", PUBLIC_WORLD)\n\tfmt.Println(\"Happy\", PUBLIC_PI, \"Day\")" + } + ] diff --git a/tenets/codelingo/code-review-comments/camel-case-constants/test.go b/tenets/codelingo/code-review-comments/camel-case-constants/test.go new file mode 100644 index 00000000..b4d9aff5 --- /dev/null +++ b/tenets/codelingo/code-review-comments/camel-case-constants/test.go @@ -0,0 +1,31 @@ +package main + +import "fmt" + +const Pi = 3.14 +const PI = 3.14 +const PIVALUE = 3.14 //ISSUE +const private_Pi = 3.14 //ISSUE +const Public_Pi = 3.14 //ISSUE +const PUBLIC_PI = 3.14 //ISSUE + +func main() { + const World = "World" + fmt.Println("Hello", World) + fmt.Println("Happy", Pi, "Day") + fmt.Println("Happy", PI, "Day") + fmt.Println("Happy", PIVALUE, "Day") + + const private_World = "World" //ISSUE + fmt.Println("Hello", private_World) + fmt.Println("Happy", private_Pi, "Day") + + const Public_World = "World" //ISSUE + fmt.Println("Hello", Public_World) + fmt.Println("Happy", Public_Pi, "Day") + + const PUBLIC_WORLD = "World" //ISSUE + fmt.Println("Hello", PUBLIC_WORLD) + fmt.Println("Happy", PUBLIC_PI, "Day") + +} diff --git a/tenets/codelingo/code-review-comments/context-first-arg/codelingo.yaml b/tenets/codelingo/code-review-comments/context-first-arg/codelingo.yaml index 960af5b4..84d40b65 100644 --- a/tenets/codelingo/code-review-comments/context-first-arg/codelingo.yaml +++ b/tenets/codelingo/code-review-comments/context-first-arg/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: context-first-arg - flows: + actions: codelingo/docs: title: Context as First Argument body: | @@ -16,16 +16,16 @@ tenets: Consider moving Context to the front. query: | import codelingo/ast/go - - go.field_list(depth = any): - sibling_order == 0 - go.field: + go.func_type(depth = any): + go.field_list: sibling_order == 0 - go.names: - go.ident - @review comment - go.selector_expr: - go.ident: - name == "context" - go.ident: - name == "Context" \ No newline at end of file + go.field: + sibling_order > 0 + go.names: + go.ident + @review comment + go.selector_expr: + go.ident: + name == "context" + go.ident: + name == "Context" \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/context-first-arg/example.go b/tenets/codelingo/code-review-comments/context-first-arg/example.go index b765a6ff..2906ccc0 100644 --- a/tenets/codelingo/code-review-comments/context-first-arg/example.go +++ b/tenets/codelingo/code-review-comments/context-first-arg/example.go @@ -1,4 +1,3 @@ -//Package main is an example package package main import ( @@ -6,13 +5,28 @@ import ( ) func aFunc(ctx context.Context, a int) { - + // Do something } -func bFunc(a, b int) (con context.Context) { - +func bFunc(b int, ctx context.Context, a int) { + // Do something } func cFunc(c int, b int, ctx context.Context, a int) { + // Do something +} + +// Don't catch them in structs +type IsTypeOfParams struct { + // Value that needs to be resolve. + // Use this to decide which GraphQLObject this value maps to. + Value interface{} + + // Info is a collection of information about the current execution state. + Info ResolveInfo + // Context argument is a context value that is provided to every resolve function within an execution. + // It is commonly + // used to represent an authenticated user, or request-specific caches. + Context context.Context } diff --git a/tenets/codelingo/code-review-comments/context-first-arg/expected.json b/tenets/codelingo/code-review-comments/context-first-arg/expected.json old mode 100755 new mode 100644 index c7562915..ad0d76b8 --- a/tenets/codelingo/code-review-comments/context-first-arg/expected.json +++ b/tenets/codelingo/code-review-comments/context-first-arg/expected.json @@ -1,7 +1,7 @@ [ { - "Comment": "Most functions that use a Context should accept it as their first parameter.", - "Filename": "code-review-comments/context-first-arg/example.go", + "Comment": "Most functions that use a Context should accept it as their first parameter. \nConsider moving Context to the front.\n", + "Filename": "example.go", "Line": 16, "Snippet": "}\n\nfunc cFunc(c int, b int, ctx context.Context, a int) {\n\n}" } diff --git a/tenets/codelingo/code-review-comments/context-in-struct/codelingo.yaml b/tenets/codelingo/code-review-comments/context-in-struct/codelingo.yaml new file mode 100644 index 00000000..7d1d8244 --- /dev/null +++ b/tenets/codelingo/code-review-comments/context-in-struct/codelingo.yaml @@ -0,0 +1,37 @@ +tenets: + - name: context-in-struct + actions: + codelingo/review: + comment: Don't add a Context member to a struct type; instead add a ctx parameter to each method on that type that needs to pass it along. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#contexts) + codelingo/docs: + title: Context in Struct + body: | + Values of the context.Context type carry security credentials, tracing information, deadlines, and cancellation signals across API and process boundaries. Go programs pass Contexts explicitly along the entire function call chain from incoming RPCs and HTTP requests to outgoing requests. + + Most functions that use a Context should accept it as their first parameter: + + `func F(ctx context.Context, /* other arguments */) {}` + + A function that is never request-specific may use context.Background(), but err on the side of passing a Context even if you think you don't need to. The default case is to pass a Context; only use context.Background() directly if you have a good reason why the alternative is a mistake. + + Don't add a Context member to a struct type; instead add a ctx parameter to each method on that type that needs to pass it along. The one exception is for methods whose signature must match an interface in the standard library or in a third party library. + + Don't create custom Context types or use interfaces other than Context in function signatures. + + If you have application data to pass around, put it in a parameter, in the receiver, in globals, or, if it truly belongs there, in a Context value. + + Contexts are immutable, so it's fine to pass the same ctx to multiple calls that share the same deadline, cancellation signal, credentials, parent trace, etc. + query: | + import codelingo/ast/go + + go.struct_type(depth = any): + go.field_list: + go.field: + go.names + @review comment + go.selector_expr: + go.ident: + # It's unnecessary to confirm that this refers to the actual context package, as any similarly named package should conform to the same requirements + name == "context" + go.ident: + name == "Context" diff --git a/tenets/codelingo/code-review-comments/context-in-struct/expected.json b/tenets/codelingo/code-review-comments/context-in-struct/expected.json new file mode 100755 index 00000000..3ffd78ac --- /dev/null +++ b/tenets/codelingo/code-review-comments/context-in-struct/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Don't add a Context member to a struct type; instead add a ctx parameter to each method on that type that needs to pass it along.", + "Filename": "main.go", + "Line": 8, + "Snippet": "type Example struct {\n\tName string\n\tCtx context.Context\n}\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/context-in-struct/main.go b/tenets/codelingo/code-review-comments/context-in-struct/main.go new file mode 100644 index 00000000..5fd62a75 --- /dev/null +++ b/tenets/codelingo/code-review-comments/context-in-struct/main.go @@ -0,0 +1,15 @@ +package main + +import "context" + +// Example is an example struct +type Example struct { + Name string + Ctx context.Context +} + +func main() { + var ex Example + ex.Ctx = context.Background() + ex.Name = "Example" +} diff --git a/tenets/codelingo/code-review-comments/declare-empty-slice/codelingo.yaml b/tenets/codelingo/code-review-comments/declare-empty-slice/codelingo.yaml new file mode 100644 index 00000000..0ce58fd0 --- /dev/null +++ b/tenets/codelingo/code-review-comments/declare-empty-slice/codelingo.yaml @@ -0,0 +1,57 @@ +funcs: + - name: fixSliceType + type: resolver + body: | + function(sliceType) { + sliceType = sliceType.split(' ').join(''); + // This check is for empty interface or struct, which include `{}` as part of their type definition + if (sliceType.substring(sliceType.length - 4, sliceType.length) == "{}{}" && (sliceType.indexOf("interface") !== 0 || sliceType.indexOf("struct") !== 0)) + return sliceType.substring(0, sliceType.indexOf("{")) + "{}" + return sliceType.substring(0, sliceType.indexOf("{")) + } + +tenets: + - name: declare-empty-slice + actions: + codelingo/review: + comment: Declare {{sliceName}} as a nil slice, with `var {{sliceName}} {{fixSliceType(sliceType)}} [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices) + codelingo/rewrite: + codelingo/docs: + title: Declaring Empty Slices + body: | + When declaring an empty slice, prefer + + ```go + var t []string + ``` + + over + + ```go + t := []string{} + ``` + + The former declares a nil slice value, while the latter is non-nil but zero-length. They are functionally equivalent-their `len` and `cap` are both zero `tbut the nil slice is the preferred style. + + Note that there are limited circumstances where a non-nil but zero-length slice is preferred, such as when encoding JSON objects (a `nil` slice encodes to `null`, while `[]string{}` encodes to the JSON array `[]`). + + When designing interfaces, avoid making a distinction between a nil slice and a non-nil, zero-length slice, as this can lead to subtle programming errors. + + For more discussion about nil in Go see Francesc Campoy's talk [Understanding Nil](https://www.youtube.com/watch?v=ynoY2xz-F8s). + query: | + import codelingo/ast/go + + @review comment + @rewrite --replace "var {{sliceName}} {{fixSliceType(sliceType)}}" + go.assign_stmt(depth = any): + tok == ":=" + go.lhs: + go.ident: + name as sliceName + go.rhs: + raw as sliceType + start_offset + end_offset + go.composite_lit: + child_count == 1 + go.array_type diff --git a/tenets/codelingo/code-review-comments/declare-empty-slice/expected.json b/tenets/codelingo/code-review-comments/declare-empty-slice/expected.json new file mode 100644 index 00000000..eb48077c --- /dev/null +++ b/tenets/codelingo/code-review-comments/declare-empty-slice/expected.json @@ -0,0 +1,44 @@ +[ + { + "Comment": "Declare personSlice as a nil slice, with `var personSlice []Person [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 14, + "Snippet": "\nfunc main() {\n\tpersonSlice := []Person{} //Issue\n\tstrSlice := []string{} //Issue\n\tintSlice1 := []int{} //Issue" + }, + { + "Comment": "Declare s2 as a nil slice, with `var s2 [][]struct{} [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 23, + "Snippet": "\n\ts1 := []struct{}{} //ISSUE\n\ts2 := [][]struct{}{} //ISSUE\n\n\tpersonSlice = append(personSlice, Person{FirstName: \"John\", LastName: \"Snow\", Age: 45})" + }, + { + "Comment": "Declare strSlice as a nil slice, with `var strSlice []string [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 15, + "Snippet": "func main() {\n\tpersonSlice := []Person{} //Issue\n\tstrSlice := []string{} //Issue\n\tintSlice1 := []int{} //Issue\n\tintSlice2 := []int{1, 2}" + }, + { + "Comment": "Declare intSlice1 as a nil slice, with `var intSlice1 []int [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 16, + "Snippet": "\tpersonSlice := []Person{} //Issue\n\tstrSlice := []string{} //Issue\n\tintSlice1 := []int{} //Issue\n\tintSlice2 := []int{1, 2}\n" + }, + { + "Comment": "Declare i1 as a nil slice, with `var i1 []interface{} [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 19, + "Snippet": "\tintSlice2 := []int{1, 2}\n\n\ti1 := []interface{}{} //ISSUE\n\ti2 := [][]interface{}{} //ISSUE\n" + }, + { + "Comment": "Declare i2 as a nil slice, with `var i2 [][]interface{} [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 20, + "Snippet": "\n\ti1 := []interface{}{} //ISSUE\n\ti2 := [][]interface{}{} //ISSUE\n\n\ts1 := []struct{}{} //ISSUE" + }, + { + "Comment": "Declare s1 as a nil slice, with `var s1 []struct{} [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices)", + "Filename": "test.go", + "Line": 22, + "Snippet": "\ti2 := [][]interface{}{} //ISSUE\n\n\ts1 := []struct{}{} //ISSUE\n\ts2 := [][]struct{}{} //ISSUE\n" + } +] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/declare-empty-slice/test.go b/tenets/codelingo/code-review-comments/declare-empty-slice/test.go new file mode 100644 index 00000000..b068ebc0 --- /dev/null +++ b/tenets/codelingo/code-review-comments/declare-empty-slice/test.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" +) + +type Person struct { + FirstName string + LastName string + Age int +} + +func main() { + personSlice := []Person{} //Issue + strSlice := []string{} //Issue + intSlice1 := []int{} //Issue + intSlice2 := []int{1, 2} + + i1 := []interface{}{} //ISSUE + i2 := [][]interface{}{} //ISSUE + + s1 := []struct{}{} //ISSUE + s2 := [][]struct{}{} //ISSUE + + personSlice = append(personSlice, Person{FirstName: "John", LastName: "Snow", Age: 45}) + strSlice = append(strSlice, "test") + intSlice1 = append(intSlice1, 1) + + fmt.Println(personSlice, strSlice, intSlice1, intSlice2) + + personSlice = []Person{} + strSlice = []string{} + intSlice1 = []int{} + intSlice2 = []int{} + + fmt.Println(personSlice, strSlice, intSlice1, intSlice2) + fmt.Println(i1, i2) + fmt.Println(s1, s2) +} diff --git a/tenets/codelingo/code-review-comments/do-not-discard-errors/README.md b/tenets/codelingo/code-review-comments/do-not-discard-errors/README.md index 007a258c..b9fe0a40 100644 --- a/tenets/codelingo/code-review-comments/do-not-discard-errors/README.md +++ b/tenets/codelingo/code-review-comments/do-not-discard-errors/README.md @@ -1,3 +1,5 @@ # do-not-discard-errors +do-not-discard-errors finds and comments on discarded errors. It is different from [return-discarded-errors](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/return-discarded-errors/codelingo.yaml) which rewrites and only checks inside functions. + _by codelingo, part of their Code Review Comments bundle_ diff --git a/tenets/codelingo/code-review-comments/do-not-discard-errors/codelingo.yaml b/tenets/codelingo/code-review-comments/do-not-discard-errors/codelingo.yaml index 6db852d2..aa2cbef3 100644 --- a/tenets/codelingo/code-review-comments/do-not-discard-errors/codelingo.yaml +++ b/tenets/codelingo/code-review-comments/do-not-discard-errors/codelingo.yaml @@ -1,27 +1,19 @@ tenets: - name: do-not-discard-errors - flows: + actions: codelingo/docs: title: Do Not Discard Errors body: | - Do not discard errors using _ variables. If a function returns an error, - check it to make sure the function succeeded. Handle the error, return it, or, - in truly exceptional situations, panic. + Do not discard errors using _ variables. If a function returns an error, check it to make sure the function succeeded. Handle the error, return it, or, in truly exceptional situations, panic. codelingo/review: comment: | - Do not discard errors using _ variables. If ignoring the err is intentional, add a justification in a comment. + Do not discard errors using _ variables. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#handle-errors). query: | import codelingo/ast/go - # This tenet should also check if the error - # discarding is mentioned in a comment above. - # See issues.yaml for more info - @review comment - go.block_stmt(depth = any): - go.assign_stmt(depth = any): - go.lhs: - go.ident: - name == "_" - type == "error" - + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name == "_" + type == "error" \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/do-not-discard-errors/example.go b/tenets/codelingo/code-review-comments/do-not-discard-errors/example.go index 6d4c465a..98a25cab 100644 --- a/tenets/codelingo/code-review-comments/do-not-discard-errors/example.go +++ b/tenets/codelingo/code-review-comments/do-not-discard-errors/example.go @@ -8,11 +8,41 @@ import ( func main() { fmt.Println("Hello, playground") - a, _ := example() + a, _ := example() // ISSUE fmt.Println(a) - fmt.Println(err) + b, _ := one() + fmt.Println(b) + + example() // ISSUE + if true { + trickyReturnExample() // ISSUE + } +} + +func passing() error { + _, err := example() + if err != nil { + return err + } + + i, err := example() + if err != nil { + return err + } + + _ = i + return nil } func example() (int, error) { return 1, errors.New("some error") } + +func one() (int, int) { + return 0, 1 +} + +func trickyReturnExample() (int, *string, string, bool, error) { + i, _ := example() // ISSUE + return i, nil, "hello", true, nil +} diff --git a/tenets/codelingo/code-review-comments/do-not-discard-errors/expected.json b/tenets/codelingo/code-review-comments/do-not-discard-errors/expected.json index 779ab5f2..c575eab6 100755 --- a/tenets/codelingo/code-review-comments/do-not-discard-errors/expected.json +++ b/tenets/codelingo/code-review-comments/do-not-discard-errors/expected.json @@ -1,8 +1,14 @@ [ { - "Comment": "Do not discard errors using _ variables. If a function returns an error, \ncheck it to make sure the function succeeded. Handle the error, return it, or, \nin truly exceptional situations, panic.\n", - "Filename": "code-review-comments/do-not-discard-errors/example.go", - "Line": 9, - "Snippet": ")\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n\ta, _ := example()\n\tfmt.Println(a)\n\tfmt.Println(err)\n}\n\nfunc example() (int, error) {" + "Comment": "Do not discard errors using _ variables. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#handle-errors).\n", + "Filename": "example.go", + "Line": 11, + "Snippet": "func main() {\n\tfmt.Println(\"Hello, playground\")\n\ta, _ := example() // ISSUE\n\tfmt.Println(a)\n\tb, _ := one()" + }, + { + "Comment": "Do not discard errors using _ variables. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#handle-errors).\n", + "Filename": "example.go", + "Line": 46, + "Snippet": "\nfunc trickyReturnExample() (int, *string, string, bool, error) {\n\ti, _ := example() // ISSUE\n\treturn i, nil, \"hello\", true, nil\n}" } ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/do-not-discard-errors/issues.yaml b/tenets/codelingo/code-review-comments/do-not-discard-errors/issues.yaml deleted file mode 100644 index 8a7ddb96..00000000 --- a/tenets/codelingo/code-review-comments/do-not-discard-errors/issues.yaml +++ /dev/null @@ -1,2 +0,0 @@ -issues: - - https://github.com/codelingo/codelingo/issues/242 \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/do-not-ignore-errors/codelingo.yaml b/tenets/codelingo/code-review-comments/do-not-ignore-errors/codelingo.yaml new file mode 100644 index 00000000..e8d67df0 --- /dev/null +++ b/tenets/codelingo/code-review-comments/do-not-ignore-errors/codelingo.yaml @@ -0,0 +1,47 @@ +tenets: + - name: do-not-ignore-errors + actions: + codelingo/rewrite: + errorHandler: place holder + codelingo/docs: + title: Do Not Ignore Errors + body: | + Do not ignore errors. If a function returns an error, check it to make sure the function succeeded. Handle the error, return it, or, in truly exceptional situations, panic. + codelingo/review: + comment: | + If a function returns an error, check it to make sure the function succeeded. rac {{ returnArgCount }} ep {{ errorPosition }}From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#handle-errors). + query: | + import codelingo/ast/go + + go.block_stmt(depth = any): # The call expression is a child of block_stmt without any intervening facts, such as go.assign_stmt. + go.list: + go.expr_stmt: + @review comment + @rewrite --replace "{{ HandleError(returnArgCount, errorPosition, callExpr) }}" + go.call_expr: + raw as callExpr + edge("calls"): + go.func_decl: + go.func_type: + go.field_list + go.field_list: + child_count as returnArgCount + go.field: + sibling_order as errorPosition + go.ident: + name == "error" +funcs: + - name: HandleError + type: resolver + body: | + function (returnArgCount, errorPosition, callExpr) { + var handler = "\t" + for (pos = 0; pos < returnArgCount; pos++) { + handler += (pos == errorPosition ? "err": "_") + handler += (pos == returnArgCount - 1 ? " := " : " , ") + } + + handler += callExpr + handler += "\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}" + return handler + } \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/do-not-ignore-errors/example.go b/tenets/codelingo/code-review-comments/do-not-ignore-errors/example.go new file mode 100644 index 00000000..e19082fd --- /dev/null +++ b/tenets/codelingo/code-review-comments/do-not-ignore-errors/example.go @@ -0,0 +1,48 @@ +//Package main is an example package +package main + +import ( + "errors" + "fmt" +) + +func main() { + fmt.Println("Hello, playground") + a, _ := example() + fmt.Println(a) + b, _ := one() + fmt.Println(b) + + example() // ISSUE + if true { + trickyReturnExample() + } +} + +func passing() error { + _, err := example() + if err != nil { + return err + } + + i, err := example() + if err != nil { + return err + } + + _ = i + return nil +} + +func example() (int, error) { + return 1, errors.New("some error") +} + +func one() (int, int) { + return 0, 1 +} + +func trickyReturnExample() (int, *string, string, bool, error) { + i, _ := example() // ISSUE + return i, nil, "hello", true, nil +} diff --git a/tenets/codelingo/code-review-comments/do-not-ignore-errors/expected.json b/tenets/codelingo/code-review-comments/do-not-ignore-errors/expected.json new file mode 100755 index 00000000..f503f029 --- /dev/null +++ b/tenets/codelingo/code-review-comments/do-not-ignore-errors/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "If a function returns an error, check it to make sure the function succeeded. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#handle-errors).\n", + "Filename": "example.go", + "Line": 16, + "Snippet": "\tfmt.Println(b)\n\n\texample() // ISSUE\n\tif true {\n\t\ttrickyReturnExample()" + }, + { + "Comment": "If a function returns an error, check it to make sure the function succeeded. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#handle-errors).\n", + "Filename": "example.go", + "Line": 18, + "Snippet": "\texample() // ISSUE\n\tif true {\n\t\ttrickyReturnExample()\n\t}\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/go-error-fmt/codelingo.yaml b/tenets/codelingo/code-review-comments/go-error-fmt/codelingo.yaml index 4f9f33b4..0fabea72 100644 --- a/tenets/codelingo/code-review-comments/go-error-fmt/codelingo.yaml +++ b/tenets/codelingo/code-review-comments/go-error-fmt/codelingo.yaml @@ -3,13 +3,69 @@ funcs: type: asserter body: | function (a) { - var dot = a.charAt(a.length - 1) === '.' - var cap = a.charAt(0) >= "A" && a.charAt(0) <= "Z" - return dot || cap + if (a.charAt(a.length - 1) === '.') { + return true + } + + var first = a.split(" ")[0] + if (first === first.toUpperCase()) { + return false + } + + // We ignore errors with words containing capitals anywhere in first word + // unless the only capital is at the start. This prevents acronyms or + // (most) code-related words being caught + first = first.substring(1) + for (var i = 0; i < first.length; i++) { + if (first.charAt(i) === first.charAt(i).toUpperCase()) { + return false + } + } + + return a.charAt(0) >= "A" && a.charAt(0) <= "Z" + } + - name: fixErrorFormat + type: resolver + body: | + function(text) { + function escapeGoFormat(text) { + return text.replace(new RegExp("([\\\"])", "g"), "\\$1") + } + + function surroundWithQuotes(text) { + return "\"" + text + "\"" + } + + function uncapitalizeFirstLetter(text) { + return text.charAt(0).toLowerCase() + text.slice(1); + } + + function removeTrailingPunctuation(text) { + var punc = /[.;:!? ]/ + while (punc.exec(text.charAt(text.length - 1))) { + text = text.substring(0, text.length - 1) + } + return text + } + + var first = text.split(" ")[0] + if (first === first.toUpperCase() || (first === "I")) { + return surroundWithQuotes(escapeGoFormat(removeTrailingPunctuation(text))) + } + + first = first.substring(1) + for (var i = 0; i < first.length; i++) { + if (first.charAt(i) === first.charAt(i).toUpperCase()) { + return surroundWithQuotes(escapeGoFormat(removeTrailingPunctuation(text))) + } + } + + return surroundWithQuotes(escapeGoFormat(uncapitalizeFirstLetter(removeTrailingPunctuation(text)))) } tenets: - name: go-error-fmt - flows: + actions: + codelingo/rewrite: codelingo/docs: title: Go Error Format body: | @@ -35,7 +91,10 @@ tenets: name == "Errorf" go.args: @review comment + @rewrite --replace "{{fixErrorFormat(strValue)}}" go.basic_lit: + sibling_order == 0 kind == "string" value as strValue isInvalidErrName(strValue) + diff --git a/tenets/codelingo/code-review-comments/go-error-fmt/example.go b/tenets/codelingo/code-review-comments/go-error-fmt/example.go index 811f0b92..b9e9de48 100644 --- a/tenets/codelingo/code-review-comments/go-error-fmt/example.go +++ b/tenets/codelingo/code-review-comments/go-error-fmt/example.go @@ -1,8 +1,14 @@ // Package main is an example package package main +import "fmt" + func main() { fmt.Errorf("something bad.") fmt.Errorf("this is okay") fmt.Errorf("This is not okay") + fmt.Errorf("THIS is okay") + fmt.Errorf("API call thing.") + fmt.Errorf("THIS is not okay.") + fmt.Errorf("this should not be caught: %q. Expecting %q", req.Header.Method(), "POST") } diff --git a/tenets/codelingo/code-review-comments/go-error-fmt/expected.json b/tenets/codelingo/code-review-comments/go-error-fmt/expected.json deleted file mode 100644 index 0b80d78f..00000000 --- a/tenets/codelingo/code-review-comments/go-error-fmt/expected.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "Comment": "Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context.", - "Filename": "golang/go-error-fmt/example.go", - "Line": 6, - "Snippet": " fmt.Errorf(\"something bad.\")\n fmt.Errorf(\"this is okay\")\n fmt.Errorf(\"This is not okay\")\n}" - }, - { - "Comment": "Error strings should not be capitalized (unless beginning with proper nouns or acronyms) or end with punctuation, since they are usually printed following other context.", - "Filename": "golang/go-error-fmt/example.go", - "Line": 4, - "Snippet": "\nfunc main() {\n fmt.Errorf(\"something bad.\")\n fmt.Errorf(\"this is okay\")\n fmt.Errorf(\"This is not okay\")" - } - ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/lingo_bundle.yaml b/tenets/codelingo/code-review-comments/lingo_bundle.yaml index 6298f7d2..c6bef291 100644 --- a/tenets/codelingo/code-review-comments/lingo_bundle.yaml +++ b/tenets/codelingo/code-review-comments/lingo_bundle.yaml @@ -1,11 +1,22 @@ description: Best Practices for Golang from Code Review Comments. version: 0.0.0 tenets: - - avoid-renaming-imports - - context-first-arg - - do-not-discard-errors - - go-error-fmt - - use-crypto-rand +- avoid-meaningless-package-names +- avoid-renaming-imports +- camel-case-constants +- context-first-arg +- context-in-struct +- declare-empty-slice +- do-not-discard-errors +- go-error-fmt +- no-custom-context +- receiver-name-short-reflect-identity +- return-discarded-errors +- same-receiver-name +- short-name-limited-scope +- short-reader-variable +- single-letter-loop-variable +- use-crypto-rand tags: - - golang - - go +- golang +- go diff --git a/tenets/codelingo/code-review-comments/no-custom-context/codelingo.yaml b/tenets/codelingo/code-review-comments/no-custom-context/codelingo.yaml new file mode 100644 index 00000000..0aae725a --- /dev/null +++ b/tenets/codelingo/code-review-comments/no-custom-context/codelingo.yaml @@ -0,0 +1,35 @@ +tenets: + - name: no-custom-context + actions: + codelingo/review: + comment: Don't create custom Context types or use interfaces other than Context in function signatures. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#contexts) + codelingo/docs: + title: No Custom Context + body: | + Values of the context.Context type carry security credentials, tracing information, deadlines, and cancellation signals across API and process boundaries. Go programs pass Contexts explicitly along the entire function call chain from incoming RPCs and HTTP requests to outgoing requests. + + Most functions that use a Context should accept it as their first parameter: + + func F(ctx context.Context, /* other arguments */) {} + A function that is never request-specific may use context.Background(), but err on the side of passing a Context even if you think you don't need to. The default case is to pass a Context; only use context.Background() directly if you have a good reason why the alternative is a mistake. + + Don't add a Context member to a struct type; instead add a ctx parameter to each method on that type that needs to pass it along. The one exception is for methods whose signature must match an interface in the standard library or in a third party library. + + Don't create custom Context types or use interfaces other than Context in function signatures. + + If you have application data to pass around, put it in a parameter, in the receiver, in globals, or, if it truly belongs there, in a Context value. + + Contexts are immutable, so it's fine to pass the same ctx to multiple calls that share the same deadline, cancellation signal, credentials, parent trace, etc. + query: | + import codelingo/ast/go + + @review comment + go.type_spec(depth = any): + any_of: + go.ident: + name == "Context" + go.ident: + name == "context" + any_of: + go.struct_type + go.interface_type diff --git a/tenets/codelingo/code-review-comments/no-custom-context/example/struct.go b/tenets/codelingo/code-review-comments/no-custom-context/example/struct.go new file mode 100644 index 00000000..82802b1c --- /dev/null +++ b/tenets/codelingo/code-review-comments/no-custom-context/example/struct.go @@ -0,0 +1,8 @@ +package example + +import "context" + +type Context struct { + Ctx context.Context + Extra string +} diff --git a/tenets/codelingo/code-review-comments/no-custom-context/expected.json b/tenets/codelingo/code-review-comments/no-custom-context/expected.json new file mode 100755 index 00000000..4bc8efc6 --- /dev/null +++ b/tenets/codelingo/code-review-comments/no-custom-context/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Don't create custom Context types or use interfaces other than Context in function signatures. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#contexts)", + "Filename": "interface.go", + "Line": 6, + "Snippet": "\n// Context is a custom context interface\ntype Context interface {\n\tDeadline() (deadline time.Time, ok bool)\n\tDone() \u003c-chan struct{}\n\tErr() error\n\tValue(key interface{}) interface{}\n\tString() string\n}\n\nfunc main() {" + }, + { + "Comment": "Don't create custom Context types or use interfaces other than Context in function signatures. From [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#contexts)", + "Filename": "example/struct.go", + "Line": 5, + "Snippet": "import \"context\"\n\ntype Context struct {\n\tCtx context.Context\n\tExtra string\n}\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/no-custom-context/interface.go b/tenets/codelingo/code-review-comments/no-custom-context/interface.go new file mode 100644 index 00000000..cad6ec05 --- /dev/null +++ b/tenets/codelingo/code-review-comments/no-custom-context/interface.go @@ -0,0 +1,15 @@ +package main + +import "time" + +// Context is a custom context interface +type Context interface { + Deadline() (deadline time.Time, ok bool) + Done() <-chan struct{} + Err() error + Value(key interface{}) interface{} + String() string +} + +func main() { +} diff --git a/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/codelingo.yaml b/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/codelingo.yaml new file mode 100644 index 00000000..cd061e83 --- /dev/null +++ b/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/codelingo.yaml @@ -0,0 +1,51 @@ +funcs: + - name: isNotAbbr + type: asserter + body: | + function(receiverName, typeName) { + rc = receiverName.toLowerCase() + tn = typeName.toLowerCase() + if(rc.length > 2) + return true + for (i = 0; i < rc.length; i++) { + if(tn.indexOf(rc[i]) === -1) + return true + } + return false + } + +tenets: + - name: receiver-name-short-reflect-identity + actions: + codelingo/review: + comment: The name of a method's receiver "{{receiverName}}" should be short and also a reflection of its identity "{{typeName}}" [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names) + codelingo/docs: + title: Method's receiver name + body: | + The name of a method's receiver should be a reflection of its identity; often a one or two letter + abbreviation of its type suffices (such as "c" or "cl" for "Client"). Don't use generic names such as + "me", "this" or "self", identifiers typical of object-oriented languages that gives the method a + special meaning. In Go, the receiver of a method is just another parameter and therefore, should + be named accordingly. The name need not be as descriptive as that of a method argument, as its + role is obvious and serves no documentary purpose. It can be very short as it will appear on + almost every line of every method of the type; familiarity admits brevity. + + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + @review comment + go.field_list: + go.field: + go.names: + go.ident: + name as receiverName + any_of: + go.ident: + name as typeName + isNotAbbr(receiverName,typeName) + go.star_expr: + go.ident: + name as typeName + isNotAbbr(receiverName,typeName) + diff --git a/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/expected.json b/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/expected.json new file mode 100644 index 00000000..f47495c9 --- /dev/null +++ b/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "The name of a method's receiver \"m\" should be short and also a reflection of its identity \"TestStruct\" [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)", + "Filename": "test.go", + "Line": 19, + "Snippet": "}\n\nfunc (m TestStruct) Method4() { //ISSUE\n\tfmt.Println(\"Hello from Method4\")\n}" + }, + { + "Comment": "The name of a method's receiver \"test\" should be short and also a reflection of its identity \"TestStruct\" [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)", + "Filename": "test.go", + "Line": 15, + "Snippet": "}\n\nfunc (test TestStruct) Method3() { //ISSUE\n\tfmt.Println(\"Hello from Method3\")\n}" + }, + { + "Comment": "The name of a method's receiver \"td\" should be short and also a reflection of its identity \"TestStruct\" [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)", + "Filename": "test.go", + "Line": 23, + "Snippet": "}\n\nfunc (td TestStruct) Method5() { //ISSUE\n\tfmt.Println(\"Hello from Method5\")\n}" + } +] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/test.go b/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/test.go new file mode 100644 index 00000000..9c0f44a7 --- /dev/null +++ b/tenets/codelingo/code-review-comments/receiver-name-short-reflect-identity/test.go @@ -0,0 +1,35 @@ +package main + +import "fmt" + +type TestStruct struct{} + +func (t TestStruct) Method1() { + fmt.Println("Hello from Method1") +} + +func (ts TestStruct) Method2() { + fmt.Println("Hello from Method2") +} + +func (test TestStruct) Method3() { //ISSUE + fmt.Println("Hello from Method3") +} + +func (m TestStruct) Method4() { //ISSUE + fmt.Println("Hello from Method4") +} + +func (td TestStruct) Method5() { //ISSUE + fmt.Println("Hello from Method5") +} + +func main() { + t := TestStruct{} + t.Method1() + t.Method2() + t.Method3() + t.Method4() + t.Method5() + +} diff --git a/tenets/codelingo/code-review-comments/return-discarded-errors/README.md b/tenets/codelingo/code-review-comments/return-discarded-errors/README.md new file mode 100644 index 00000000..00389f55 --- /dev/null +++ b/tenets/codelingo/code-review-comments/return-discarded-errors/README.md @@ -0,0 +1,5 @@ +# return-discarded-errors + +return-discarded-errors finds errors that are discarded in functions and rewrites them so that they are returned instead. It is different from [do-not-discard-errors](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/do-not-discard-errors/codelingo.yaml) which also checks the top level scope and does not rewrite. + +_by codelingo, part of their Code Review Comments bundle_ diff --git a/tenets/codelingo/code-review-comments/return-discarded-errors/codelingo.yaml b/tenets/codelingo/code-review-comments/return-discarded-errors/codelingo.yaml new file mode 100644 index 00000000..bb07feea --- /dev/null +++ b/tenets/codelingo/code-review-comments/return-discarded-errors/codelingo.yaml @@ -0,0 +1,61 @@ +tenets: + - name: return-discarded-errors + vars: + returnError: | + if err != nil { + return {{ zeroValues(args) }} + } + actions: + codelingo/review: + comment: Don't discard errors. + codelingo/rewrite: + place: holder + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.func_type: + @place holder + go.field_list: + raw as args + sibling_order == 1 + @rewrite --line --append "{{returnError}}" + @review comment + go.assign_stmt(depth = any): + go.lhs: + @rewrite "err" + go.ident: + name == "_" + type == "error" +funcs: + - name: zeroValues + type: resolver + body: | + function (args) { + if (args[0] == "(") { + args = args.slice(1) + } + + if (args[args.length-1] == ")") { + args = args.slice(0, -1) + } + + return args.split(", ").map(function(param){ + switch(param) { + case "string": + return "\"\"" + case "int", "int32", "int64", "float", "float32", "float64": + return "0" + case "bool": + return "false" + case "error": + return "err" + default: + if (param.indexOf("*") != -1) { + return "nil" + } + return param + "{}" + } + }).join(", ") + } + diff --git a/tenets/codelingo/code-review-comments/return-discarded-errors/example.go b/tenets/codelingo/code-review-comments/return-discarded-errors/example.go new file mode 100644 index 00000000..9b869e89 --- /dev/null +++ b/tenets/codelingo/code-review-comments/return-discarded-errors/example.go @@ -0,0 +1,58 @@ +//Package main is an example package +package main + +import ( + "errors" + "fmt" +) + +func main() { + fmt.Println("Hello, playground") + a, _ := example() // ISSUE + fmt.Println(a) +} + +func passing() error { + _, err := example() + if err != nil { + return err + } + + i, err := example() + if err != nil { + return err + } + + _ = i + return nil +} + +func example() (int, error) { + return 1, errors.New("some error") +} + +type a struct{} + +func trickyReturnExample() (a, *a, int, *string, string, bool, error) { + i, _ := example() // ISSUE + return a{}, nil, i, nil, "hello", true, nil +} + +func singleExample() error { + i, _ := example() // ISSUE + _ = i + return nil +} + +func nonErrorDiscard() (int, error) { + _, err := example() + if err != nil { + return 0, err + } + return 0, nil +} + +func (b *a) methodExample() (int, error) { + i, _ := example() // ISSUE + return i, nil +} diff --git a/tenets/codelingo/code-review-comments/same-receiver-name/codelingo.yaml b/tenets/codelingo/code-review-comments/same-receiver-name/codelingo.yaml new file mode 100644 index 00000000..1c482b98 --- /dev/null +++ b/tenets/codelingo/code-review-comments/same-receiver-name/codelingo.yaml @@ -0,0 +1,50 @@ +funcs: + - name: notEqual + type: asserter + body: | + function(name1, name2) { + return name1 != name2 + } + +tenets: + - name: same-receiver-name + actions: + codelingo/review: + comment: If you call the receiver "{{receiverName1}}" in "{{methodName1}}" method, don't call it "{{receiverName2}}" in "{{methodName2}}" method. Be consistent. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names) + + + query: | + import codelingo/ast/go + go.dir(depth = any): + go.func_decl(depth = any): + go.field_list: + go.field: + go.names: + go.ident: + name as receiverName1 + any_of: + go.ident: + name as typeName + go.star_expr: + go.ident: + name as typeName + go.ident: + name as methodName1 + go.func_decl(depth = any): + go.field_list: + go.field: + go.names: + go.ident: + name as receiverName2 + notEqual(receiverName1, receiverName2) + any_of: + go.ident: + name == typeName + go.star_expr: + go.ident: + name == typeName + + @review comment + go.ident: + name as methodName2 + notEqual(methodName1, methodName2) diff --git a/tenets/codelingo/code-review-comments/same-receiver-name/expected.json b/tenets/codelingo/code-review-comments/same-receiver-name/expected.json new file mode 100644 index 00000000..090db497 --- /dev/null +++ b/tenets/codelingo/code-review-comments/same-receiver-name/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "If you call the receiver \"this\" in \"Method2\" method, don't call it \"t\" in \"Method1\" method. Be consistent. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)", + "Filename": "test.go", + "Line": 8, + "Snippet": "type T2 struct{}\n\nfunc (t T1) Method1() { //ISSUE\n\tfmt.Println(\"Hello from Method1\")\n}" + }, + { + "Comment": "If you call the receiver \"t\" in \"Method1\" method, don't call it \"this\" in \"Method2\" method. Be consistent. [as specified in Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)", + "Filename": "test.go", + "Line": 12, + "Snippet": "}\n\nfunc (this T1) Method2() { //ISSUE\n\tfmt.Println(\"Hello from Method2\")\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/same-receiver-name/test.go b/tenets/codelingo/code-review-comments/same-receiver-name/test.go new file mode 100644 index 00000000..cbb60a40 --- /dev/null +++ b/tenets/codelingo/code-review-comments/same-receiver-name/test.go @@ -0,0 +1,32 @@ +package main + +import "fmt" + +type T1 struct{} +type T2 struct{} + +func (t T1) Method1() { //ISSUE + fmt.Println("Hello from Method1") +} + +func (this T1) Method2() { //ISSUE + fmt.Println("Hello from Method2") +} + +func (t T2) Method3() { + fmt.Println("Hello from Method3") +} + +func (t T2) Method4() { + fmt.Println("Hello from Method4") +} + +func main() { + t1 := T1{} + t1.Method1() + t1.Method2() + t2 := T2{} + t2.Method3() + t2.Method4() + +} diff --git a/tenets/codelingo/code-review-comments/short-name-limited-scope/codelingo.yaml b/tenets/codelingo/code-review-comments/short-name-limited-scope/codelingo.yaml new file mode 100644 index 00000000..fa9e952b --- /dev/null +++ b/tenets/codelingo/code-review-comments/short-name-limited-scope/codelingo.yaml @@ -0,0 +1,47 @@ +funcs: + - name: isLong + type: asserter + body: | + function(varName) { + return varName.length > 1 + } + - name: isLimitedScope + type: asserter + body: | + function(childCount) { + return childCount <= 10 // This is an approximate; we assume if the number of children of a block is less equal than 10, it is a short one + } +tenets: + - name: short-name-limited-scope + actions: + codelingo/review: + comment: Local variable "{{varName}}" with a limited scope (less than 10 lines) should be short rather than long [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names) + codelingo/docs: + title: Short Name Limited Scope + body: | + Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. + + + query: | + import codelingo/ast/go + + go.block_stmt(depth = any): + go.list: + child_count as childCount + isLimitedScope(childCount) + any_of: + @review comment + go.assign_stmt(depth = any): + tok == ":=" + go.lhs: + go.ident: + name as varName + isLong(varName) + @review comment + go.decl_stmt(depth = any): + go.gen_decl: + go.value_spec: + go.names: + go.ident: + name as varName + isLong(varName) diff --git a/tenets/codelingo/code-review-comments/short-name-limited-scope/expected.json b/tenets/codelingo/code-review-comments/short-name-limited-scope/expected.json new file mode 100644 index 00000000..9a054178 --- /dev/null +++ b/tenets/codelingo/code-review-comments/short-name-limited-scope/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "Local variable \"f1\" with a limited scope (less than 10 lines) should be short rather than long [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 19, + "Snippet": "\tfmt.Println(intVar)\n\n\tf1 := \"short\" //ISSUE\n\tfmt.Println(f1)\n}" + }, + { + "Comment": "Local variable \"boolVar\" with a limited scope (less than 10 lines) should be short rather than long [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 13, + "Snippet": "\tfmt.Println(b, c)\n\n\tvar boolVar = true //ISSUE\n\tfmt.Println(boolVar)\n" + }, + { + "Comment": "Local variable \"intVar\" with a limited scope (less than 10 lines) should be short rather than long [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 16, + "Snippet": "\tfmt.Println(boolVar)\n\n\tvar intVar int //ISSUE\n\tfmt.Println(intVar)\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/short-name-limited-scope/test.go b/tenets/codelingo/code-review-comments/short-name-limited-scope/test.go new file mode 100644 index 00000000..7ba1416f --- /dev/null +++ b/tenets/codelingo/code-review-comments/short-name-limited-scope/test.go @@ -0,0 +1,21 @@ +package main + +import "fmt" + +func main() { + + var a = "initial" + fmt.Println(a) + + var b, c int = 1, 2 + fmt.Println(b, c) + + var boolVar = true //ISSUE + fmt.Println(boolVar) + + var intVar int //ISSUE + fmt.Println(intVar) + + f1 := "short" //ISSUE + fmt.Println(f1) +} diff --git a/tenets/codelingo/code-review-comments/short-reader-variable/codelingo.yaml b/tenets/codelingo/code-review-comments/short-reader-variable/codelingo.yaml new file mode 100644 index 00000000..53f7a5a0 --- /dev/null +++ b/tenets/codelingo/code-review-comments/short-reader-variable/codelingo.yaml @@ -0,0 +1,41 @@ +funcs: + - name: isNotValid + type: asserter + body: | + function(varName) { + return varName.length > 2 || varName[0] != 'r' || (varName[0] == 'r' && (varName[1] < '0' || varName[1] > '9')) + } + + +tenets: + - name: short-reader-variable + actions: + codelingo/review: + comment: reader variable "{{varName}}" should be a single letter "r", or two letters starting with "r" followed by a number for more readability [See Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names) + # We currently accept two letters rather than a single letter to allow use of r1...rn. This helps readibility if two readers are needed in the same scope + + codelingo/docs: + title: Short Reader variables + body: | + + Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex. + + The basic rule. the further from its declaration that a name is used, the more descriptive the name must be. For a method receiver, one or two letters is sufficient. Common variables such as loop indices and readers can be a single letter (i, r). More unusual things and global variables need more descriptive names. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + @review comment + go.assign_stmt (depth = any): + tok == ":=" + go.lhs: + go.ident: + name as varName + isNotValid(varName) + go.rhs: + go.call_expr: + go.selector_expr: + go.ident: + name == "strings" + go.ident: + name == "NewReader" diff --git a/tenets/codelingo/code-review-comments/short-reader-variable/expected.json b/tenets/codelingo/code-review-comments/short-reader-variable/expected.json new file mode 100644 index 00000000..f2d69f5e --- /dev/null +++ b/tenets/codelingo/code-review-comments/short-reader-variable/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "reader variable \"rr\" should be a single letter \"r\", or two letters starting with \"r\" followed by a number for more readability [See Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 34, + "Snippet": "\t}\n\n\trr := strings.NewReader(\"Hello, Reader!\") //ISSUE\n\n\tb = make([]byte, 8)" + }, + { + "Comment": "reader variable \"reader\" should be a single letter \"r\", or two letters starting with \"r\" followed by a number for more readability [See Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 46, + "Snippet": "\t}\n\n\treader := strings.NewReader(\"Hello, Reader!\") //ISSUE\n\n\tb = make([]byte, 8)" + }, + { + "Comment": "reader variable \"b1\" should be a single letter \"r\", or two letters starting with \"r\" followed by a number for more readability [See Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 58, + "Snippet": "\t}\n\n\tb1 := strings.NewReader(\"Hello, Reader!\") //ISSUE\n\n\tb = make([]byte, 8)" + } +] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/short-reader-variable/test.go b/tenets/codelingo/code-review-comments/short-reader-variable/test.go new file mode 100644 index 00000000..6e644436 --- /dev/null +++ b/tenets/codelingo/code-review-comments/short-reader-variable/test.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "io" + "strings" +) + +func main() { + r := strings.NewReader("Hello, Reader!") + + b := make([]byte, 8) + for { + n, err := r.Read(b) + fmt.Printf("n = %v err = %v b = %v\n", n, err, b) + fmt.Printf("b[:n] = %q\n", b[:n]) + if err == io.EOF { + break + } + } + + r1 := strings.NewReader("Hello, Reader!") + + b = make([]byte, 8) + for { + n, err := r1.Read(b) + fmt.Printf("n = %v err = %v b = %v\n", n, err, b) + fmt.Printf("b[:n] = %q\n", b[:n]) + if err == io.EOF { + break + } + } + + rr := strings.NewReader("Hello, Reader!") //ISSUE + + b = make([]byte, 8) + for { + n, err := rr.Read(b) + fmt.Printf("n = %v err = %v b = %v\n", n, err, b) + fmt.Printf("b[:n] = %q\n", b[:n]) + if err == io.EOF { + break + } + } + + reader := strings.NewReader("Hello, Reader!") //ISSUE + + b = make([]byte, 8) + for { + n, err := reader.Read(b) + fmt.Printf("n = %v err = %v b = %v\n", n, err, b) + fmt.Printf("b[:n] = %q\n", b[:n]) + if err == io.EOF { + break + } + } + + b1 := strings.NewReader("Hello, Reader!") //ISSUE + + b = make([]byte, 8) + for { + n, err := b1.Read(b) + fmt.Printf("n = %v err = %v b = %v\n", n, err, b) + fmt.Printf("b[:n] = %q\n", b[:n]) + if err == io.EOF { + break + } + } +} diff --git a/tenets/codelingo/code-review-comments/single-letter-loop-variable/codelingo.yaml b/tenets/codelingo/code-review-comments/single-letter-loop-variable/codelingo.yaml new file mode 100644 index 00000000..1e8b0630 --- /dev/null +++ b/tenets/codelingo/code-review-comments/single-letter-loop-variable/codelingo.yaml @@ -0,0 +1,41 @@ +funcs: + - name: isLong + type: asserter + body: | + function(varName) { + return varName.length > 1 + } + +tenets: + - name: single-letter-loop-variable + actions: + codelingo/review: + comment: loop index variable "{{varName}}" should be a single letter [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names) + codelingo/docs: + title: Local variables with limited scope are prefered to be short + body: | + + Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex. + + The basic rule. the further from its declaration that a name is used, the more descriptive the name must be. For a method receiver, one or two letters is sufficient. Common variables such as loop indices and readers can be a single letter (i, r). More unusual things and global variables need more descriptive names. + + + query: | + import codelingo/ast/go + + # Currently, we only raise an issue for a loop variable in 1) a for loop with all three parts 2) a for range loop + any_of: + go.for_stmt(depth = any): + child_count == 4 + @review comment + go.assign_stmt: + go.lhs: + go.ident: + name as varName + isLong(varName) + go.range_stmt(depth = any): + @review comment + go.ident: + sibling_order == 0 + name as varName + isLong(varName) diff --git a/tenets/codelingo/code-review-comments/single-letter-loop-variable/expected.json b/tenets/codelingo/code-review-comments/single-letter-loop-variable/expected.json new file mode 100644 index 00000000..894fbd15 --- /dev/null +++ b/tenets/codelingo/code-review-comments/single-letter-loop-variable/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "loop index variable \"index\" should be a single letter [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 14, + "Snippet": "\n\tsum = 0\n\tfor index := 0; index \u003c 10; index++ { //ISSUE\n\t\tsum += index\n\t}" + }, + { + "Comment": "loop index variable \"index\" should be a single letter [as specified in Code Review Comments](https://github.com/codelingo/codelingo/blob/master/tenets/codelingo/code-review-comments/FULL_TEXT.md#variable-names)", + "Filename": "test.go", + "Line": 57, + "Snippet": "\n\tsum = 0\n\tfor index, num := range nums { //ISSUE\n\t\tsum += index + num\n\t}" + } +] \ No newline at end of file diff --git a/tenets/codelingo/code-review-comments/single-letter-loop-variable/test.go b/tenets/codelingo/code-review-comments/single-letter-loop-variable/test.go new file mode 100644 index 00000000..21db80fe --- /dev/null +++ b/tenets/codelingo/code-review-comments/single-letter-loop-variable/test.go @@ -0,0 +1,61 @@ +package main + +import "fmt" + +func main() { + + sum := 0 + for i := 0; i < 10; i++ { + sum += i + } + fmt.Println(sum) + + sum = 0 + for index := 0; index < 10; index++ { //ISSUE + sum += index + } + fmt.Println(sum) + + sum = 0 + for index := 0; ; index++ { + if index > 10 { + break + } + sum += index + } + fmt.Println(sum) + + sum = 0 + index := 0 + for ; ; index++ { + if index > 10 { + break + } + sum += index + } + fmt.Println(sum) + + sum = 0 + index = 0 + for { + if index > 10 { + break + } + sum += index + index++ + } + fmt.Println(sum) + + nums := []int{2, 3, 4} + sum = 0 + for _, num := range nums { + sum += num + } + fmt.Println(sum) + + sum = 0 + for index, num := range nums { //ISSUE + sum += index + num + } + fmt.Println(sum) +} diff --git a/tenets/codelingo/code-review-comments/use-crypto-rand/codelingo.yaml b/tenets/codelingo/code-review-comments/use-crypto-rand/codelingo.yaml index f70585b7..ee31f1ee 100644 --- a/tenets/codelingo/code-review-comments/use-crypto-rand/codelingo.yaml +++ b/tenets/codelingo/code-review-comments/use-crypto-rand/codelingo.yaml @@ -7,9 +7,9 @@ funcs: } tenets: - name: use-crypto-rand - flows: + actions: codelingo/docs: - title: Use Crypto rand + title: Use Crypto Rand body: | Do not use package math/rand to generate keys, even throwaway ones. Unseeded, the generator is completely predictable. diff --git a/tenets/codelingo/drupal/lingo_bundle.yaml b/tenets/codelingo/drupal/lingo_bundle.yaml new file mode 100644 index 00000000..23c4ad62 --- /dev/null +++ b/tenets/codelingo/drupal/lingo_bundle.yaml @@ -0,0 +1,7 @@ +description: Best practices for PHP from Drupal. +version: 0.0.0 +tenets: +- upper-case-constant-values +tags: +- php +- drupal diff --git a/tenets/codelingo/drupal/upper-case-constant-values/.codelingoignore b/tenets/codelingo/drupal/upper-case-constant-values/.codelingoignore new file mode 100644 index 00000000..47782043 --- /dev/null +++ b/tenets/codelingo/drupal/upper-case-constant-values/.codelingoignore @@ -0,0 +1 @@ +orig.php diff --git a/tenets/codelingo/drupal/upper-case-constant-values/README.md b/tenets/codelingo/drupal/upper-case-constant-values/README.md new file mode 100644 index 00000000..b6f37c25 --- /dev/null +++ b/tenets/codelingo/drupal/upper-case-constant-values/README.md @@ -0,0 +1,3 @@ +# upper-case-constant-values + +_by codelingo, part of their Drupal bundle_ diff --git a/tenets/codelingo/drupal/upper-case-constant-values/codelingo.yaml b/tenets/codelingo/drupal/upper-case-constant-values/codelingo.yaml new file mode 100644 index 00000000..acc98f4b --- /dev/null +++ b/tenets/codelingo/drupal/upper-case-constant-values/codelingo.yaml @@ -0,0 +1,33 @@ +funcs: + - name: isIncorrectCase + type: asserter + body: | + function(varName) { + var constants = ['TRUE', 'FALSE', 'NULL'] + + if (constants.indexOf(varName.toUpperCase()) !== -1) { + return varName !== varName.toUpperCase() + } + return false + } + - name: fixCase + type: resolver + body: | + function(varName) { + return varName.toUpperCase() + } +tenets: + - name: upper-case-constant-values + actions: + codelingo/review: + comment: Constants should always be all-uppercase, with underscores to separate words. (This includes pre-defined PHP constants like TRUE, FALSE, and NULL.) + codelingo/rewrite: + query: | + import codelingo/ast/php + + php.expr_constfetch(depth = any): + @review comment + @rewrite --replace "{{fixCase(varName)}}" + php.name_fullyqualified: + name as varName + isIncorrectCase(varName) diff --git a/tenets/codelingo/drupal/upper-case-constant-values/example-orig.php b/tenets/codelingo/drupal/upper-case-constant-values/example-orig.php new file mode 100644 index 00000000..722b8c75 --- /dev/null +++ b/tenets/codelingo/drupal/upper-case-constant-values/example-orig.php @@ -0,0 +1,15 @@ +", - "_.*_", - ] - var re = new RegExp(exps.join("|"), "i") - return re.test(text) + var words = text.split(" ") + for (var i = 0; i < words.length; i++) { + if (((words[i].charAt(0) === "_" && words[i].charAt(words[i].length - 1) === "_") || + (words[i].charAt(0) === "<" && words[i].charAt(words[i].length - 1) === ">")) && + words[i].length !== 1) { + return true + } + } + return false } tenets: - name: avoid-annotations-in-comments - flows: + actions: codelingo/docs: title: Avoid Annotations in Comments body: | diff --git a/tenets/codelingo/effective-go/comment-first-word-as-subject/codelingo.yaml b/tenets/codelingo/effective-go/comment-first-word-as-subject/codelingo.yaml index 12dafff9..3b0af071 100644 --- a/tenets/codelingo/effective-go/comment-first-word-as-subject/codelingo.yaml +++ b/tenets/codelingo/effective-go/comment-first-word-as-subject/codelingo.yaml @@ -1,4 +1,23 @@ funcs: + - name: isNotATestFile + type: asserter + body: | + function(filename) { + // ES5 workaround + // TODO: don't use ES5 + String.prototype.endsWith = String.prototype.endsWith || function(suffix) { + return this.indexOf(suffix, this.length - suffix.length) >= 0; + }; + + return !filename.endsWith("_test.go") + } + - name: isExported + type: asserter + body: | + function(name) { + // TODO: support unicode uppercase + return !!name.match(/^[A-Z]/) + } - name: isValid type: asserter body: | @@ -13,51 +32,287 @@ funcs: - name: fixComment type: resolver body: | - function(text, name) { + function (text, name) { + var keywordchars = "A-Za-z_0-9"; + + function testre(pat, text) { + var re = new RegExp(pat) + return re.test(text) + } + + function uc(s) { + return s.toUpperCase(); + } + + function lc(s) { + return s.toLowerCase(); + } + + function capitalize(s) { + return uc(s.charAt(0)) + s.slice(1); + } + + function uncapitalize(s) { + return lc(s.charAt(0)) + s.slice(1); + } + + function removeInitialWhitespace(s) { + return s.replace(new RegExp("^ *", "i"), "") + } + + function prepend_name_and_maybe_colonify(s, name) { + var carry_on = false; + + plurifiable_verbs.forEach(function(verb) { + if (testre("^" + lc(verb) + "s", lc(s))) { + carry_on = true; + } + }) + + verbs.forEach(function(verb) { + if (testre("^" + lc(verb), lc(s))) { + carry_on = true; + } + }) + + if (carry_on) { + return name + " " + s; + } else { + return name + ": " + s; + } + } + + function setDifference(A, B) { + A.filter(function(x) { return B.indexOf(x) < 0 }) + } + + function firstWord(s) { + return s.split(" ")[0] + } + + function filterEmpty(arr) { + // ES6: subwords = subwords.filter(Boolean) + return arr.filter(function(v){return v!==''}); + } + + function splitCamel(s) { + return filterEmpty(s.split(/([A-Z][a-z]*)/)); + } + + // Makes an educated guess to whether or not the first word is an old function name + function smellsLikeOldName(first_word) { + // Ignore when there are special characters. Allow a couple of () though so it can be rewritten + if (testre("[^" + keywordchars + "()]", first_word)) { + return false; + } + + subwords = splitCamel(first_word); + name_subwords = splitCamel(name); + + // Ignore words without camel case, unless it is all upper case. + // Sometimes the 'new' first word is not on old function name + if ((subwords.length === 1) && ! (uc(subwords[0]) === subwords[0])) { + return false; + } + + same = setIntersection(subwords, name_subwords) + + var is_old_name = false; + + if (same.length > 0) { + return true; + } else { + //Split first word into camel-cased subwords + subwords.forEach(function(subword) { + if (subword.length < 4) return; + // If start of comment smells like an old name + if (lc(subword).indexOf(lc(name)) !== -1 || lc(name).indexOf(lc(subword)) !== -1) { + is_old_name = true; + } + }) + } + + return is_old_name; + } + + // Replaces the first word with a new function name + function updateOld(s, name) { + var words = s.split(" ") + words[0] = name; + return words.join(" ") + } + + // modal_verbs etc. + var verbs = ["will", "does", "is", "exist"]; + var plurifiable_verbs = ["split", "purge", "remove", "test", "ensure", "replace", "get", "start", + "validate", "execute", "demonstrate", "compare", "hook", + "report", "take", "clear", "handle", "decode", + "unregister", "capture", "retrieve", "declare", "check", + "return", "render", "add", "read", "write"]; + + + // If one of these tokens appear followed by an s, it should NOT be treated as a verb + var verb_plural_blacklist = ["lot", "alia", "arg", "resource", + "event", "GET"]; + + var nouns = ["test", "function", "method"]; + var adjectives = ["various", "exists"]; + var determiners = ["the", "this"]; + + function capitalizeLeadingDeterminer(s) { + determiners.forEach(function(determiner) { + s = s.replace(new RegExp("^" + determiner + " +", "i"), capitalize(determiner) + " ") + }) + return s + } + + function fixLeadingVerb(s) { + // Make these replacements before removing from the front + plurifiable_verbs.forEach(function(verb) { + if (! includes(verb_plural_blacklist, firstWord(s))) { + var pverb = verb + "s" + + s = s.replace(new RegExp("^" + verb + "s\? +", "i"), pverb + " ") + + adjectives.forEach(function(adjective) { + s = s.replace(new RegExp("^" + pverb + " " + adjective + " ", "i"), pverb + " " + lc(adjective) + " ") + }) + } + }) + + return s + } + + function includes(arr,obj) { + // As of ECMAScript 2016 you can use includes() + return (arr.indexOf(obj) != -1); + } + + function setUnion (x, y) { + var obj = {}; + for (var i = x.length-1; i >= 0; -- i) + obj[x[i]] = x[i]; + for (var i = y.length-1; i >= 0; -- i) + obj[y[i]] = y[i]; + var res = [] + for (var k in obj) { + if (obj.hasOwnProperty(k)) // <-- optional + res.push(obj[k]); + } + return res; + } + + function setIntersection(a, b) { + var ai=0, bi=0; + var result = []; + + while( ai < a.length && bi < b.length ) + { + if (a[ai] < b[bi] ){ ai++; } + else if (a[ai] > b[bi] ){ bi++; } + else /* they're equal */ + { + result.push(a[ai]); + ai++; + bi++; + } + } + + return result; + } + + function removeThisIfVerb(s) { + setUnion(verbs, plurifiable_verbs).forEach(function(verb) { + s = s.replace(new RegExp("^This " + verb + " ", "i"), verb + " ") + }) + return s + } + + function rewordComment(s, name, commentChar) { + // Removing 'The' is fine, unless it is replaced by something + // without a capital letter because that may be a common noun, such + // as 'distance', there the function comment is on a 'Distance' + // function. + s = s.replace(new RegExp("^(The) " + capitalize(name) + " "), "") + + // Make these replacements before removing from the front + s = fixLeadingVerb(s) + s = capitalizeLeadingDeterminer(s) + + nouns.forEach(function(noun) { + s = s.replace(new RegExp("^(This) " + noun + " ", "i"), "") + }) + + first_word = firstWord(s); + + if (smellsLikeOldName(first_word)) { + // could optionally fix verb here but don't + s = updateOld(s, name) + return s; + } + + s = fixLeadingVerb(s) + s = s.replace(new RegExp("^[a-z]+([A-Z]+[a-z0-9]+)+ "), "") // Remove leading camelCase + s = s.replace(new RegExp("^([A-Z]+[a-z0-9]+){2,} "), "") // Remove leading PascalCase. Must come after remove name + s = removeThisIfVerb(s) + s = s.replace(new RegExp("^Purpose ", "i"), "The purpose ") + s = prepend_name_and_maybe_colonify(s, name) + + return s + } + var commentChar = text.substring(0, 2) - text = text.substring(2).trim() - var words = text.split(" ") - var result - var firstWord = words[0].split(/([A-Z][a-z]*)/) - firstWord.forEach(function(word) { - if (word.length < 4) return - // If start of comment smells like an old name - if (word.toLowerCase().indexOf(name.toLowerCase()) !== -1 || - name.toLowerCase().indexOf(word.toLowerCase()) !== -1) { - words[0] = name - result = words.join(" ") - } - }) - if (result == undefined) { - return commentChar + " " + name + " " + words.join(" ") - } - return commentChar + " " + result + var commentString = text.substring(2).trim() + + commentString = commentString.replace(new RegExp("^[/*]* ", "i"), " ") // Remove extra leading comment chars + + // Do nothing if comment starts with + // - non-alphanumerics + // - test + // - LABEL: + // - TODO + if ( + (! testre("^[A-Za-z]", commentString)) || + (testre("^test", lc(commentString))) || + (testre("^[a-zA-Z]+:", commentString)) || + (testre("^TODO ", commentString)) || + (testre("^A-Za-z_", firstWord(commentString))) + ) { + + return text + } + + commentString = removeInitialWhitespace(commentString) + return commentChar + " " + rewordComment(commentString, name, commentChar) } tenets: - name: comment-first-word-as-subject - flows: + actions: codelingo/docs: title: Comment First Word as Subject body: | - Doc comments work best as complete sentences, which allow a wide variety of automated presentations. - The first sentence should be a one-sentence summary that starts with the name being declared. + Doc comments work best as complete sentences, which allow a wide variety of automated presentations. + The first sentence should be a summary that starts with the name being declared. codelingo/review: comment: | - The first sentence should be a one-sentence summary that starts with the name ({{funcName}}) being declared. + Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name ({{funcName}}) being declared. + From [effective go](https://golang.org/doc/effective_go.html#commentary). codelingo/rewrite: place: holder query: | import codelingo/ast/go - # Only look at exported functions - go.func_decl(depth = any): - go.comment_group: - @review comment - @rewrite --replace "{{fixComment(commText, funcName)}}" - go.comment: - sibling_order == 0 - text as commText - go.ident: - name as funcName - public == "true" - isValid(commText, funcName) \ No newline at end of file + go.file(depth = any): + filename as filename + isNotATestFile(filename) + go.func_decl(depth = any): + go.comment_group: + @review comment + @rewrite --replace "{{fixComment(commText, funcName)}}" + go.comment: + sibling_order == 0 + text as commText + go.ident: + name as funcName + isExported(funcName) + isValid(commText, funcName) diff --git a/tenets/codelingo/effective-go/comment-first-word-as-subject/example.go b/tenets/codelingo/effective-go/comment-first-word-as-subject/example.go index ec1d866a..6336406f 100644 --- a/tenets/codelingo/effective-go/comment-first-word-as-subject/example.go +++ b/tenets/codelingo/effective-go/comment-first-word-as-subject/example.go @@ -5,7 +5,7 @@ import ( "fmt" ) -func main() { +func main() { fmt.Println("Hello, playground") // This comment not on a func decl } @@ -14,10 +14,35 @@ func main() { */ func foo() {} + +/* Foo is the first word + */ +func Foo() {} + /* This func comment should begin with 'bar' */ func bar() {} +/* This func comment should begin with 'Bar' + */ +func Bar() {} + // This func comment should begin with 'baz' // and we should not worry about this line func baz() {} + +// This func comment should begin with 'Baz' +// and we should not worry about this line +func Baz() {} + +// This is called by a xyz +func qux() {} + +// This is called by a xyz +func Qux() {} + +// The quux will handle xyz +func quux() {} + +// The Quux will handle xyz +func Quux() {} \ No newline at end of file diff --git a/tenets/codelingo/effective-go/comment-first-word-as-subject/example_test.go b/tenets/codelingo/effective-go/comment-first-word-as-subject/example_test.go new file mode 100644 index 00000000..6336406f --- /dev/null +++ b/tenets/codelingo/effective-go/comment-first-word-as-subject/example_test.go @@ -0,0 +1,48 @@ +// Package main used for testing the tenet +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, playground") + // This comment not on a func decl +} + +/* foo is the first word + */ +func foo() {} + + +/* Foo is the first word + */ +func Foo() {} + +/* This func comment should begin with 'bar' + */ +func bar() {} + +/* This func comment should begin with 'Bar' + */ +func Bar() {} + +// This func comment should begin with 'baz' +// and we should not worry about this line +func baz() {} + +// This func comment should begin with 'Baz' +// and we should not worry about this line +func Baz() {} + +// This is called by a xyz +func qux() {} + +// This is called by a xyz +func Qux() {} + +// The quux will handle xyz +func quux() {} + +// The Quux will handle xyz +func Quux() {} \ No newline at end of file diff --git a/tenets/codelingo/effective-go/comment-first-word-as-subject/expected.json b/tenets/codelingo/effective-go/comment-first-word-as-subject/expected.json old mode 100755 new mode 100644 index 2e514b42..27289e10 --- a/tenets/codelingo/effective-go/comment-first-word-as-subject/expected.json +++ b/tenets/codelingo/effective-go/comment-first-word-as-subject/expected.json @@ -1,14 +1,26 @@ [ { - "Comment": "The first sentence should be a one-sentence summary that starts with the name (baz) being declared.\n", + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Baz) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", "Filename": "example.go", - "Line": 21, - "Snippet": "func bar() {}\n\n// This func comment should begin with 'baz'\n// and we should not worry about this line\nfunc baz() {}" + "Line": 34, + "Snippet": "func baz() {}\n\n// This func comment should begin with 'Baz'\n// and we should not worry about this line\nfunc Baz() {}" }, { - "Comment": "The first sentence should be a one-sentence summary that starts with the name (bar) being declared.\n", + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Quux) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", "Filename": "example.go", - "Line": 17, - "Snippet": "func foo() {}\n\n/* This func comment should begin with 'bar'\n */\nfunc bar() {}\n" + "Line": 47, + "Snippet": "func quux() {}\n\n// The Quux will handle xyz\nfunc Quux() {}" + }, + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Qux) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", + "Filename": "example.go", + "Line": 41, + "Snippet": "func qux() {}\n\n// This is called by a xyz\nfunc Qux() {}\n" + }, + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Bar) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", + "Filename": "example.go", + "Line": 26, + "Snippet": "func bar() {}\n\n/* This func comment should begin with 'Bar'\n */\nfunc Bar() {}\n" } ] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/comment-first-word-when-empty/README.md b/tenets/codelingo/effective-go/comment-first-word-when-empty/README.md new file mode 100644 index 00000000..fc54fa32 --- /dev/null +++ b/tenets/codelingo/effective-go/comment-first-word-when-empty/README.md @@ -0,0 +1,6 @@ +# comment-first-word-when-empty + +_by codelingo, part of their effective go bundle_ + + +[Load up in the codelingo.io/playground](https://codelingo.io/playground/?repo=github.com/codelingo/hub&dir=tenets/codelingo/effective-go/comment-first-word-when-empty&tenet=codelingo/effective-go/comment-first-word-when-empty) diff --git a/tenets/codelingo/effective-go/comment-first-word-when-empty/codelingo.yaml b/tenets/codelingo/effective-go/comment-first-word-when-empty/codelingo.yaml new file mode 100644 index 00000000..a196597b --- /dev/null +++ b/tenets/codelingo/effective-go/comment-first-word-when-empty/codelingo.yaml @@ -0,0 +1,43 @@ +funcs: + - name: isNotATestFile + type: asserter + body: | + function(filename) { + // ES5 workaround + // TODO: don't use ES5 + String.prototype.endsWith = String.prototype.endsWith || function(suffix) { + return this.indexOf(suffix, this.length - suffix.length) >= 0; + }; + + return !filename.endsWith("_test.go") + } + - name: isExported + type: asserter + body: | + function(name) { + // TODO: support unicode uppercase + return !!name.match(/^[A-Z]/) + } +tenets: + - name: comment-first-word-when-empty + actions: + codelingo/review: + comment: | + Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name ({{funcName}}) being declared. + From [effective go](https://golang.org/doc/effective_go.html#commentary). + codelingo/rewrite: + place: holder + query: | + import codelingo/ast/go + + go.file(depth = any): + filename as filename + isNotATestFile(filename) + @review comment + @rewrite --prepend --line "// {{funcName}} is a function." + go.func_decl(depth = any): + exclude: + go.comment_group + go.ident: + name as funcName + isExported(funcName) diff --git a/tenets/codelingo/effective-go/comment-first-word-when-empty/example.go b/tenets/codelingo/effective-go/comment-first-word-when-empty/example.go new file mode 100644 index 00000000..dc1e02c8 --- /dev/null +++ b/tenets/codelingo/effective-go/comment-first-word-when-empty/example.go @@ -0,0 +1,3 @@ +package main + +func Unnamed() {} diff --git a/tenets/codelingo/effective-go/comment-first-word-when-empty/expected.json b/tenets/codelingo/effective-go/comment-first-word-when-empty/expected.json new file mode 100755 index 00000000..921955e9 --- /dev/null +++ b/tenets/codelingo/effective-go/comment-first-word-when-empty/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Unnamed) being declared.\n", + "Filename": "example.go", + "Line": 3, + "Snippet": "package main\n\nfunc Unnamed() {}\n" + } +] diff --git a/tenets/codelingo/effective-go/defer-close-file/codelingo.yaml b/tenets/codelingo/effective-go/defer-close-file/codelingo.yaml new file mode 100644 index 00000000..ec64cf04 --- /dev/null +++ b/tenets/codelingo/effective-go/defer-close-file/codelingo.yaml @@ -0,0 +1,55 @@ +tenets: + - name: defer-close-file + actions: + codelingo/docs: + title: Defer Close File + body: | + Deferring a call to a function such as Close has two advantages. First, it guarantees that you will never forget to close the file, a mistake that's easy to make if you later edit the function to add a new return path. Second, it means that the close sits near the open, which is much clearer than placing it at the end of the function. + TODO if a function returns the open file, follow it and check if it is closed + codelingo/review: + comment: Add `defer {{fileName}}.Close()` after opening the file `{{fileName}}`. . [as specified in Effective Go](https://golang.org/doc/effective_go.html#defer) + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + @review comment + go.assign_stmt(depth = any): + go.lhs: + go.ident: + sibling_order == 0 + name as fileName + go.rhs: + go.call_expr: + go.selector_expr: + go.ident: + name == "os" + any_of: + go.ident: + name == "Open" + go.ident: + name == "OpenFile" + exclude: + go.return_stmt(depth = any): + go.results: + go.ident: + name == fileName + go.func_type: + go.field_list: + go.field: + go.names: + go.ident: + name == fileName + go.star_expr: + go.selector_expr: + go.ident: + name == "os" + go.ident: + name == "File" + exclude: + go.defer_stmt(depth = any): + go.call_expr: + go.selector_expr: + go.ident: + name == fileName + go.ident: + name == "Close" diff --git a/tenets/codelingo/effective-go/defer-close-file/expected.json b/tenets/codelingo/effective-go/defer-close-file/expected.json new file mode 100644 index 00000000..15f817f6 --- /dev/null +++ b/tenets/codelingo/effective-go/defer-close-file/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "Add `defer f3.Close()` after opening the file `f3`. . [as specified in Effective Go](https://golang.org/doc/effective_go.html#defer)", + "Filename": "test.go", + "Line": 54, + "Snippet": "\tf2.Close()\n\n\tf3, err := os.OpenFile(\"/tmp/test.txt\", os.O_RDWR, 0644) //ISSUE\n\tcheck(err)\n\t//defer f3.Close()" + }, + { + "Comment": "Add `defer f1.Close()` after opening the file `f1`. . [as specified in Effective Go](https://golang.org/doc/effective_go.html#defer)", + "Filename": "test.go", + "Line": 37, + "Snippet": "func main() {\n\n\tf1, err := os.Open(\"/tmp/test.txt\") //ISSUE\n\tcheck(err)\n\t//defer f1.Close()" + }, + { + "Comment": "Add `defer f.Close()` after opening the file `f`. . [as specified in Effective Go](https://golang.org/doc/effective_go.html#defer)", + "Filename": "test.go", + "Line": 20, + "Snippet": "\nfunc test2() {\n\tf, err := os.Open(\"/tmp/test.txt\") //ISSUE\n\tcheck(err)\n\t//defer f.Close()" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/defer-close-file/test.go b/tenets/codelingo/effective-go/defer-close-file/test.go new file mode 100644 index 00000000..0cef9e1b --- /dev/null +++ b/tenets/codelingo/effective-go/defer-close-file/test.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "os" +) + +func check(e error) { + if e != nil { + panic(e) + } +} +func test1() *os.File { + f, err := os.Open("/tmp/test.txt") + check(err) + return f +} + +func test2() { + f, err := os.Open("/tmp/test.txt") //ISSUE + check(err) + //defer f.Close() + b := make([]byte, 5) + n, err := f.Read(b) + check(err) + fmt.Printf("%d bytes: %s\n", n, string(b)) +} + +func test3() (f *os.File) { + f, err := os.Open("/tmp/test.txt") + check(err) + return +} + +func main() { + + f1, err := os.Open("/tmp/test.txt") //ISSUE + check(err) + //defer f1.Close() + b1 := make([]byte, 5) + n1, err := f1.Read(b1) + check(err) + fmt.Printf("%d bytes: %s\n", n1, string(b1)) + + f2, err := os.Open("/tmp/test.txt") + check(err) + defer f2.Close() + b2 := make([]byte, 5) + n2, err := f2.Read(b2) + check(err) + fmt.Printf("%d bytes: %s\n", n2, string(b2)) + f2.Close() + + f3, err := os.OpenFile("/tmp/test.txt", os.O_RDWR, 0644) //ISSUE + check(err) + //defer f3.Close() + b3 := make([]byte, 5) + n3, err := f3.Read(b3) + check(err) + fmt.Printf("%d bytes: %s\n", n3, string(b3)) + +} diff --git a/tenets/codelingo/effective-go/good-package-name/codelingo.yaml b/tenets/codelingo/effective-go/good-package-name/codelingo.yaml index fa8a4294..c02e0ef3 100644 --- a/tenets/codelingo/effective-go/good-package-name/codelingo.yaml +++ b/tenets/codelingo/effective-go/good-package-name/codelingo.yaml @@ -19,7 +19,7 @@ funcs: } tenets: - name: good-package-name - flows: + actions: codelingo/docs: title: Good Package Name body: | diff --git a/tenets/codelingo/effective-go/initialize-using-composite-literal/codelingo.yaml b/tenets/codelingo/effective-go/initialize-using-composite-literal/codelingo.yaml new file mode 100644 index 00000000..347e5964 --- /dev/null +++ b/tenets/codelingo/effective-go/initialize-using-composite-literal/codelingo.yaml @@ -0,0 +1,29 @@ +tenets: + - name: initialize-using-composite-literal + actions: + codelingo/docs: + title: Initialize instance using composite literal + body: | + Sometimes the zero value isn't good enough and an initializing constructor is necessary. We can simplify the code using a composite literal, which is an expression that creates a new instance each time it is evaluated. + codelingo/review: + comment: Initialize {{varName}} using a composite literal to simplify the code. [as specified in Effective Go](https://golang.org/doc/effective_go.html#composite_literals) + query: | + import codelingo/ast/go + + go.element(depth = any): + go.assign_stmt: + tok == ":=" + go.lhs: + go.ident: + name as varName + go.rhs: + go.call_expr: + go.ident: + name == "new" + @review comment + go.assign_stmt(depth=any): + tok == "=" + go.lhs: + go.selector_expr: + go.ident: + name == varName diff --git a/tenets/codelingo/effective-go/initialize-using-composite-literal/expected.json b/tenets/codelingo/effective-go/initialize-using-composite-literal/expected.json new file mode 100644 index 00000000..c31a8c14 --- /dev/null +++ b/tenets/codelingo/effective-go/initialize-using-composite-literal/expected.json @@ -0,0 +1,32 @@ +[ + { + "Comment": "Initialize f using a composite literal to simplify the code. [as specified in Effective Go](https://golang.org/doc/effective_go.html#composite_literals)", + "Filename": "test.go", + "Line": 23, + "Snippet": "\t}\n\tf := new(File)\n\tf.fd = fd //ISSUE\n\tf.name = name //ISSUE\n\treturn f" + }, + { + "Comment": "Initialize f using a composite literal to simplify the code. [as specified in Effective Go](https://golang.org/doc/effective_go.html#composite_literals)", + "Filename": "test.go", + "Line": 24, + "Snippet": "\tf := new(File)\n\tf.fd = fd //ISSUE\n\tf.name = name //ISSUE\n\treturn f\n}" + }, + { + "Comment": "Initialize p2 using a composite literal to simplify the code. [as specified in Effective Go](https://golang.org/doc/effective_go.html#composite_literals)", + "Filename": "test.go", + "Line": 46, + "Snippet": "\tp2.FirstName = \"Alice\" //ISSUE\n\tp2.LastName = \"Green\" //ISSUE\n\tp2.Age = 40 //ISSUE\n\tfmt.Println(p2)\n" + }, + { + "Comment": "Initialize p2 using a composite literal to simplify the code. [as specified in Effective Go](https://golang.org/doc/effective_go.html#composite_literals)", + "Filename": "test.go", + "Line": 44, + "Snippet": "\n\tp2 := new(Person)\n\tp2.FirstName = \"Alice\" //ISSUE\n\tp2.LastName = \"Green\" //ISSUE\n\tp2.Age = 40 //ISSUE" + }, + { + "Comment": "Initialize p2 using a composite literal to simplify the code. [as specified in Effective Go](https://golang.org/doc/effective_go.html#composite_literals)", + "Filename": "test.go", + "Line": 45, + "Snippet": "\tp2 := new(Person)\n\tp2.FirstName = \"Alice\" //ISSUE\n\tp2.LastName = \"Green\" //ISSUE\n\tp2.Age = 40 //ISSUE\n\tfmt.Println(p2)" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/initialize-using-composite-literal/test.go b/tenets/codelingo/effective-go/initialize-using-composite-literal/test.go new file mode 100644 index 00000000..ef4d926a --- /dev/null +++ b/tenets/codelingo/effective-go/initialize-using-composite-literal/test.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" +) + +type File struct { + fd int + name string +} + +type Person struct { + FirstName string + LastName string + Age int +} + +func NewFile1(fd int, name string) *File { + if fd < 0 { + return nil + } + f := new(File) + f.fd = fd //ISSUE + f.name = name //ISSUE + return f +} + +func NewFile2(fd int, name string) *File { + if fd < 0 { + return nil + } + f := File{fd, name} + return &f +} + +func main() { + fmt.Println(NewFile1(5, "Test")) + fmt.Println(NewFile2(5, "Test")) + + p1 := Person{FirstName: "John", LastName: "Snow", Age: 45} + fmt.Println(p1) + + p2 := new(Person) + p2.FirstName = "Alice" //ISSUE + p2.LastName = "Green" //ISSUE + p2.Age = 40 //ISSUE + fmt.Println(p2) + +} diff --git a/tenets/codelingo/effective-go/lingo_bundle.yaml b/tenets/codelingo/effective-go/lingo_bundle.yaml index beea189f..fd0cff97 100644 --- a/tenets/codelingo/effective-go/lingo_bundle.yaml +++ b/tenets/codelingo/effective-go/lingo_bundle.yaml @@ -1,10 +1,20 @@ description: Best Practices for Golang from Effective Go. version: 0.0.0 tenets: - - avoid-annotations-in-comments - - comment-first-word-as-subject - - package-comment - - single-method-interface-name +- avoid-annotations-in-comments +- comment-first-word-as-subject +- comment-first-word-when-empty +- defer-close-file +- good-package-name +- initialize-using-composite-literal +- loop-variable-used-in-go-routine +- no-get-in-getters-name +- package-comment +- reuse-variable-name-type-switch +- single-method-interface-name +- underscores-in-name +- unnecessary-else +- update-comment-first-word-as-subject tags: - - golang - - go +- golang +- go diff --git a/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/codelingo.yaml b/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/codelingo.yaml new file mode 100644 index 00000000..a322316f --- /dev/null +++ b/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/codelingo.yaml @@ -0,0 +1,131 @@ +funcs: + - name: isEqualtoAny + type: asserter + body: | + function(name1, name2, name3) { + return (name1 == name3 && name1 != "_" ) || (name2 == name3 && name2 != "_" ) + } + + - name: isEqual + type: asserter + body: | + function(name1, name2) { + return name1 == name2 && name1 != "_" + } + + +tenets: + - name: loop-variable-used-in-go-routine + actions: + codelingo/docs: + title: test + body: | + The loop variable is reused for each iteration, so it is shared across all goroutines. We need to make sure that it is unique for each goroutine. One way to do that, is passing the loop variable as an argument to the closure in the goroutine. + Note: This tenet assumes that loop variables are not shadowed inside goroutine. We need ssa to work to find the right loop variables in that case. + codelingo/review: + comment: Pass any loop variables used in the goroutine as an argument to the closure. [as specified in Effective Go](https://golang.org/doc/effective_go.html#channels) + + query: | + import codelingo/ast/go + + + go.file(depth = any): + any_of: + go.for_stmt(depth = any): + go.assign_stmt: + go.lhs: + go.ident: + name as varName1 + go.block_stmt: + go.list: + @review comment + go.go_stmt(depth = any): + go.call_expr: + go.func_lit: + go.block_stmt: + go.ident(depth = any): + name as varName2 + isEqual(varName1, varName2) + exclude: + go.args: + go.ident: + name as varName3 + isEqual(varName2, varName3) + exclude: + go.assign_stmt(depth = any): + tok == ":=" + go.lhs: + go.ident: + name as varName4 + go.rhs: + go.ident: + name as varName5 + isEqual(varName4, varName5) + isEqual(varName2, varName4) + go.range_stmt(depth = any): + child_count == 4 + go.ident: + sibling_order == 0 + name as varName1 + go.ident: + sibling_order == 1 + name as varName2 + go.block_stmt: + go.list: + @review comment + go.go_stmt(depth = any): + go.call_expr: + go.func_lit: + go.block_stmt: + go.ident(depth = any): + name as varName3 + isEqualtoAny(varName1, varName2, varName3) + exclude: + go.args: + go.ident: + name as varName4 + isEqual(varName3, varName4) + exclude: + go.assign_stmt(depth = any): + tok == ":=" + go.lhs: + go.ident: + name as varName5 + go.rhs: + go.ident: + name as varName6 + isEqual(varName5, varName6) + isEqual(varName3, varName5) + + + go.range_stmt(depth = any): + child_count == 3 + go.ident: + sibling_order == 0 + name as varName1 + go.block_stmt: + go.list: + @review comment + go.go_stmt(depth = any): + go.call_expr: + go.func_lit: + go.block_stmt: + go.ident(depth = any): + name as varName2 + isEqual(varName1, varName2) + exclude: + go.args: + go.ident: + name as varName3 + isEqual(varName2, varName3) + exclude: + go.assign_stmt(depth = any): + tok == ":=" + go.lhs: + go.ident: + name as varName4 + go.rhs: + go.ident: + name as varName5 + isEqual(varName4, varName5) + isEqual(varName2, varName4) diff --git a/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/expected.json b/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/expected.json new file mode 100644 index 00000000..28757097 --- /dev/null +++ b/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/expected.json @@ -0,0 +1,32 @@ +[ + { + "Comment": "Pass any loop variables used in the goroutine as an argument to the closure. [as specified in Effective Go](https://golang.org/doc/effective_go.html#channels)", + "Filename": "test.go", + "Line": 64, + "Snippet": "func Test7(values []int) {\n\tfor i := 0; i \u003c 10; i++ {\n\t\tgo func() { //ISSUE\n\t\t\tfmt.Println(values[i])\n\t\t}()\n\t}\n}" + }, + { + "Comment": "Pass any loop variables used in the goroutine as an argument to the closure. [as specified in Effective Go](https://golang.org/doc/effective_go.html#channels)", + "Filename": "test.go", + "Line": 25, + "Snippet": "func Test3(values []int, n int) {\n\tfor i, val := range values {\n\t\tgo func(val int) { //ISSUE\n\t\t\tif i \u003c n {\n\t\t\t\tfmt.Println(val)\n\t\t\t}\n\t\t}(val)\n\t}\n}" + }, + { + "Comment": "Pass any loop variables used in the goroutine as an argument to the closure. [as specified in Effective Go](https://golang.org/doc/effective_go.html#channels)", + "Filename": "test.go", + "Line": 82, + "Snippet": "\tfor i, val := range values {\n\t\tval := val\n\t\tgo func() { //ISSUE\n\t\t\tif i \u003c n {\n\t\t\t\tfmt.Println(val)\n\t\t\t}\n\t\t}()\n\t}\n}" + }, + { + "Comment": "Pass any loop variables used in the goroutine as an argument to the closure. [as specified in Effective Go](https://golang.org/doc/effective_go.html#channels)", + "Filename": "test.go", + "Line": 54, + "Snippet": "func Test6(values []int, n bool) {\n\tfor val := range values {\n\t\tgo func(n bool) { //ISSUE\n\t\t\tif n {\n\t\t\t\tfmt.Println(val)\n\t\t\t}\n\t\t}(n)\n\t}\n}" + }, + { + "Comment": "Pass any loop variables used in the goroutine as an argument to the closure. [as specified in Effective Go](https://golang.org/doc/effective_go.html#channels)", + "Filename": "test.go", + "Line": 9, + "Snippet": "func Test1(values []int) {\n\tfor val := range values {\n\t\tgo func() { //ISSUE\n\t\t\tfmt.Println(val)\n\t\t}()\n\t}\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/test.go b/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/test.go new file mode 100644 index 00000000..9457374d --- /dev/null +++ b/tenets/codelingo/effective-go/loop-variable-used-in-go-routine/test.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" +) + +func Test1(values []int) { + for val := range values { + go func() { //ISSUE + fmt.Println(val) + }() + } +} + +func Test2(values []int) { + for val := range values { + go func(val int) { + fmt.Println(val) + }(val) + } +} + +func Test3(values []int, n int) { + for i, val := range values { + go func(val int) { //ISSUE + if i < n { + fmt.Println(val) + } + }(val) + } +} + +func Test4(values []int, n int) { + for i, val := range values { + fmt.Println(i, val) + go func() { + fmt.Println(n) + }() + } +} + +func Test5(values []int, n bool) { + for val := range values { + go func(val int, n bool) { + if n { + fmt.Println(val) + } + }(val, n) + } +} + +func Test6(values []int, n bool) { + for val := range values { + go func(n bool) { //ISSUE + if n { + fmt.Println(val) + } + }(n) + } +} + +func Test7(values []int) { + for i := 0; i < 10; i++ { + go func() { //ISSUE + fmt.Println(values[i]) + }() + } +} + +func Test8(values []int) { + for val := range values { + val := val + go func() { + fmt.Println(val) + }() + } +} + +func Test9(values []int, n int) { + for i, val := range values { + val := val + go func() { //ISSUE + if i < n { + fmt.Println(val) + } + }() + } +} + +func Test10(values []int, n int) { + for i, val := range values { + val := val + i := i + go func() { + if i < n { + fmt.Println(val) + } + }() + } +} + +func main() { + values := []int{2, 3, 5, 7, 11, 13} + Test1(values) + Test2(values) + Test3(values, 4) + Test4(values, 5) + Test5(values, true) + Test6(values, true) + Test7(values) + Test8(values) + Test9(values, 4) + Test10(values, 5) +} diff --git a/tenets/codelingo/effective-go/no-get-in-getters-name/codelingo.yaml b/tenets/codelingo/effective-go/no-get-in-getters-name/codelingo.yaml new file mode 100644 index 00000000..142f8665 --- /dev/null +++ b/tenets/codelingo/effective-go/no-get-in-getters-name/codelingo.yaml @@ -0,0 +1,42 @@ +funcs: + - name: hasGet + type: asserter + body: | + function(name) { + return name.substring(0,3) === "Get" + } + + - name: fixName + type: resolver + body: | + function(name) { + return name.charAt(0).toUpperCase() + name.substring(1) + } + +tenets: + - name: no-get-in-getters-name + actions: + codelingo/docs: + title: no Get at the start of Getters name + body: | + It's neither idiomatic nor necessary to put Get into the getter's name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner. + codelingo/review: + comment: Do not use Get at the start of "{{methodName}}", use "{{fixName(varName)}}" instead [as specified in Effective Go](https://golang.org/doc/effective_go.html#Getters) + codelingo/rewrite: + query: | + import codelingo/ast/go + + @review comment + go.func_decl(depth = any): + go.field_list + go.ident: + name as methodName + hasGet(methodName) + go.block_stmt: + go.list: + go.return_stmt: + go.results: + go.selector_expr: + go.ident: + sibling_order == 1 + name as varName diff --git a/tenets/codelingo/effective-go/no-get-in-getters-name/expected.json b/tenets/codelingo/effective-go/no-get-in-getters-name/expected.json new file mode 100644 index 00000000..873a0dd4 --- /dev/null +++ b/tenets/codelingo/effective-go/no-get-in-getters-name/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Do not use Get at the start of \"GetWidth\", use \"Width\" instead [as specified in Effective Go](https://golang.org/doc/effective_go.html#Getters)", + "Filename": "test.go", + "Line": 16, + "Snippet": "}\n\nfunc (r *rect) GetWidth() int { //ISSUE\n\treturn r.width\n}\n\nfunc (r *rect) GetHeight() int { //ISSUE" + }, + { + "Comment": "Do not use Get at the start of \"GetHeight\", use \"Height\" instead [as specified in Effective Go](https://golang.org/doc/effective_go.html#Getters)", + "Filename": "test.go", + "Line": 20, + "Snippet": "}\n\nfunc (r *rect) GetHeight() int { //ISSUE\n\treturn r.height\n}\n\nfunc (r rect) perim() int {" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/no-get-in-getters-name/test.go b/tenets/codelingo/effective-go/no-get-in-getters-name/test.go new file mode 100644 index 00000000..e531bce7 --- /dev/null +++ b/tenets/codelingo/effective-go/no-get-in-getters-name/test.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "time" +) + +type rect struct { + width, height int +} + +func (r *rect) area() int { + return r.width * r.height +} + +func (r *rect) GetWidth() int { //ISSUE + return r.width +} + +func (r *rect) GetHeight() int { //ISSUE + return r.height +} + +func (r rect) perim() int { + return 2*r.width + 2*r.height +} + +func GetTime() { + fmt.Println("The time is :", time.Now()) +} + +func main() { + + r := rect{width: 10, height: 5} + + fmt.Println("width ", r.GetWidth()) + fmt.Println("height ", r.GetHeight()) + + fmt.Println("area: ", r.area()) + fmt.Println("perim:", r.perim()) + + GetTime() +} diff --git a/tenets/codelingo/effective-go/package-comment/codelingo.yaml b/tenets/codelingo/effective-go/package-comment/codelingo.yaml index c915e788..1472ad40 100644 --- a/tenets/codelingo/effective-go/package-comment/codelingo.yaml +++ b/tenets/codelingo/effective-go/package-comment/codelingo.yaml @@ -1,8 +1,8 @@ tenets: - name: package-comment - flows: + actions: codelingo/docs: - title: Package comment + title: Package Comment body: | Every package should have a package comment, a block comment preceding the package clause. For multi-file packages, the package comment only needs to be present in one file, and any one will do. diff --git a/tenets/codelingo/effective-go/reuse-variable-name-type-switch/codelingo.yaml b/tenets/codelingo/effective-go/reuse-variable-name-type-switch/codelingo.yaml new file mode 100644 index 00000000..0ef6e07a --- /dev/null +++ b/tenets/codelingo/effective-go/reuse-variable-name-type-switch/codelingo.yaml @@ -0,0 +1,32 @@ +funcs: + - name: notEqual + type: asserter + body: | + function(name1, name2) { + return name1 != name2 + } + +tenets: + - name: reuse-variable-name-type-switch + actions: + codelingo/docs: + title: Reuse the variable name in a type switch + body: | + If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It's also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case. + codelingo/review: + comment: Reuse `{{varName2}}` instead of a new variable `{{varName1}}` in the type switch. [as specified in Effective Go](https://golang.org/doc/effective_go.html#type_switch) + codelingo/rewrite: + query: | + import codelingo/ast/go + + @review comment + go.type_switch_stmt(depth = any): + go.assign_stmt: + go.lhs: + go.ident: + name as varName1 + go.rhs: + go.type_assert_expr: + go.ident: + name as varName2 + notEqual(varName1, varName2) diff --git a/tenets/codelingo/effective-go/reuse-variable-name-type-switch/expected.json b/tenets/codelingo/effective-go/reuse-variable-name-type-switch/expected.json new file mode 100644 index 00000000..d51349fb --- /dev/null +++ b/tenets/codelingo/effective-go/reuse-variable-name-type-switch/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Reuse `i` instead of a new variable `v` in the type switch. [as specified in Effective Go](https://golang.org/doc/effective_go.html#type_switch)", + "Filename": "test.go", + "Line": 6, + "Snippet": "\nfunc do(i interface{}) {\n\tswitch v := i.(type) { //ISSUE\n\tcase int:\n\t\tfmt.Printf(\"Twice %v is %v\\n\", v, v*2)\n\tcase string:\n\t\tfmt.Printf(\"%q is %v bytes long\\n\", v, len(v))\n\tdefault:\n\t\tfmt.Printf(\"I don't know about type %T!\\n\", v)\n\t}\n\n\tswitch i := i.(type) {" + } +] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/reuse-variable-name-type-switch/test.go b/tenets/codelingo/effective-go/reuse-variable-name-type-switch/test.go new file mode 100644 index 00000000..8cc3e4d5 --- /dev/null +++ b/tenets/codelingo/effective-go/reuse-variable-name-type-switch/test.go @@ -0,0 +1,29 @@ +package main + +import "fmt" + +func do(i interface{}) { + switch v := i.(type) { //ISSUE + case int: + fmt.Printf("Twice %v is %v\n", v, v*2) + case string: + fmt.Printf("%q is %v bytes long\n", v, len(v)) + default: + fmt.Printf("I don't know about type %T!\n", v) + } + + switch i := i.(type) { + case int: + fmt.Printf("Twice %v is %v\n", i, i*2) + case string: + fmt.Printf("%q is %v bytes long\n", i, len(i)) + default: + fmt.Printf("I don't know about type %T!\n", i) + } +} + +func main() { + do(21) + do("hello") + do(true) +} diff --git a/tenets/codelingo/effective-go/single-method-interface-name/codelingo.yaml b/tenets/codelingo/effective-go/single-method-interface-name/codelingo.yaml index 4f188ffc..5cdf52ee 100644 --- a/tenets/codelingo/effective-go/single-method-interface-name/codelingo.yaml +++ b/tenets/codelingo/effective-go/single-method-interface-name/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: single-method-interface-name - flows: + actions: codelingo/docs: title: Single Method Interface Name body: | diff --git a/tenets/codelingo/effective-go/underscores-in-name/codelingo.yaml b/tenets/codelingo/effective-go/underscores-in-name/codelingo.yaml index 15371a30..4fca58d0 100644 --- a/tenets/codelingo/effective-go/underscores-in-name/codelingo.yaml +++ b/tenets/codelingo/effective-go/underscores-in-name/codelingo.yaml @@ -1,9 +1,20 @@ funcs: - - name: containsUnderscores + - name: containsInvalidUnderscores type: asserter body: | function(name) { - return name.indexOf("_") !== -1 + if (name.length === 1) { + return false + } + + var location = name.indexOf("_") + if (location === -1) { + return false + } + + // Underscores are valid if they're in-between two digits + var digits = '0123456789' + return digits.indexOf(name.charAt(location + 1)) === -1 || digits.indexOf(name.charAt(location - 1)) === -1 } - name: isNotProto type: asserter @@ -13,9 +24,9 @@ funcs: } tenets: - name: underscores-in-name - flows: + actions: codelingo/review: - comment: The convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names. + comment: Use MixedCaps or mixedCaps rather than underscores to write multiword names. From [Effective Go](https://golang.org/doc/effective_go.html#mixed-caps). query: | import codelingo/ast/go go.file(depth = any): @@ -25,4 +36,4 @@ tenets: @review comment go.ident(depth = any): name as varName - containsUnderscores(varName) + containsInvalidUnderscores(varName) diff --git a/tenets/codelingo/effective-go/underscores-in-name/example.go b/tenets/codelingo/effective-go/underscores-in-name/example.go index 907cc0fa..f69e73c7 100644 --- a/tenets/codelingo/effective-go/underscores-in-name/example.go +++ b/tenets/codelingo/effective-go/underscores-in-name/example.go @@ -1,9 +1,12 @@ // Package main used for testing of tenet package main +import "fmt" + func main() { var a = 15 a_bad_name := 20 + HashSHA512_384 := func() {} // A reasonable exception var another_bad_name = "hello" aGoodVar := 2 } @@ -20,6 +23,7 @@ func a_bad_function() { type Bad_Interface interface { SomeMethod() int } + // BetterInterface used as example type BetterInterface interface { SomeMethod() int diff --git a/tenets/codelingo/effective-go/unnecessary-else/codelingo.yaml b/tenets/codelingo/effective-go/unnecessary-else/codelingo.yaml new file mode 100644 index 00000000..6070fe13 --- /dev/null +++ b/tenets/codelingo/effective-go/unnecessary-else/codelingo.yaml @@ -0,0 +1,38 @@ +funcs: + - name: isGreater + type: asserter + body: | + function(name1, name2) { + return name1 > name2 + } + +tenets: + - name: unnecessary-else + actions: + codelingo/docs: + title: Unnecessary Else + body: | + When an if statement doesn't flow into the next statement—that is, the body ends in break, continue, goto, or return—the unnecessary else is omitted. + codelingo/review: + comment: Omit unnecessary else when `if` doesn't flow into the next statement. [as specified in Effective Go](https://golang.org/doc/effective_go.html#control-structures) + codelingo/rewrite: + query: | + import codelingo/ast/go + + + go.if_stmt(depth = any): + go.block_stmt: + sibling_order as sb1 + go.list: + any_of: + go.return_stmt + go.branch_stmt: + tok == "break" + go.branch_stmt: + tok == "goto" + go.branch_stmt: + tok == "continue" + @review comment + go.block_stmt: + sibling_order as sb2 + isGreater(sb2, sb1) diff --git a/tenets/codelingo/effective-go/unnecessary-else/expected.json b/tenets/codelingo/effective-go/unnecessary-else/expected.json new file mode 100644 index 00000000..ab7c64fb --- /dev/null +++ b/tenets/codelingo/effective-go/unnecessary-else/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "Omit unnecessary else when `if` doesn't flow into the next statement. [as specified in Effective Go](https://golang.org/doc/effective_go.html#control-structures)", + "Filename": "test.go", + "Line": 13, + "Snippet": "\t\tfmt.Println(\"Hello from if\")\n\t\treturn\n\t} else { //ISSUE\n\t\tfmt.Println(\"Hello from else\")\n\t}\n}\n" + }, + { + "Comment": "Omit unnecessary else when `if` doesn't flow into the next statement. [as specified in Effective Go](https://golang.org/doc/effective_go.html#control-structures)", + "Filename": "test.go", + "Line": 42, + "Snippet": "\tif true {\n\t\tgoto end\n\t} else { //ISSUE\n\t\tfmt.Println(\"test\")\n\t}\nend:\n\tfmt.Println(\"Hello world!\")" + }, + { + "Comment": "Omit unnecessary else when `if` doesn't flow into the next statement. [as specified in Effective Go](https://golang.org/doc/effective_go.html#control-structures)", + "Filename": "test.go", + "Line": 35, + "Snippet": "\t\tif n%2 == 0 {\n\t\t\tcontinue\n\t\t} else { //ISSUE\n\t\t\tfmt.Println(n)\n\t\t}\n\t}\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/effective-go/unnecessary-else/test.go b/tenets/codelingo/effective-go/unnecessary-else/test.go new file mode 100644 index 00000000..c5e4398b --- /dev/null +++ b/tenets/codelingo/effective-go/unnecessary-else/test.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" +) + +func test1() { + x := true + + if x == true { + fmt.Println("Hello from if") + return + } else { //ISSUE + fmt.Println("Hello from else") + } +} + +func test2() { + x := false + + if x == true { + fmt.Println("Hello from if") + return + } + fmt.Println("Hello after if") + +} + +func main() { + test1() + test2() + for n := 0; n <= 5; n++ { + if n%2 == 0 { + continue + } else { //ISSUE + fmt.Println(n) + } + } + + if true { + goto end + } else { //ISSUE + fmt.Println("test") + } +end: + fmt.Println("Hello world!") + +} diff --git a/tenets/codelingo/effective-go/update-comment-first-word-as-subject/README.md b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/README.md new file mode 100644 index 00000000..060c8923 --- /dev/null +++ b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/README.md @@ -0,0 +1,6 @@ +# comment-first-word-as-subject + +_by codelingo, part of their effective go bundle_ + + +[Load up in the codelingo.io/playground](https://codelingo.io/playground/?repo=github.com/codelingo/hub&dir=tenets/codelingo/effective-go/comment-first-word-as-subject&tenet=codelingo/effective-go/comment-first-word-as-subject) diff --git a/tenets/codelingo/effective-go/update-comment-first-word-as-subject/codelingo.yaml b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/codelingo.yaml new file mode 100644 index 00000000..9bc8d79b --- /dev/null +++ b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/codelingo.yaml @@ -0,0 +1,306 @@ +funcs: + - name: isNotATestFile + type: asserter + body: | + function(filename) { + // ES5 workaround + // TODO: don't use ES5 + String.prototype.endsWith = String.prototype.endsWith || function(suffix) { + return this.indexOf(suffix, this.length - suffix.length) >= 0; + }; + + return !filename.endsWith("_test.go") + } + - name: isExported + type: asserter + body: | + function(name) { + // TODO: support unicode uppercase + return !!name.match(/^[A-Z]/) + } + - name: isValid + type: asserter + body: | + function(text, name) { + text = text.substring(2).trim() + var words = text.split(" ") + if (words[0].toLowerCase() === "deprecated") { + return false + } + return words[0].toLowerCase() !== name.toLowerCase() + } + - name: fixComment + type: resolver + body: | + function (text, name) { + var keywordchars = "A-Za-z_0-9"; + + function testre(pat, text) { + var re = new RegExp(pat) + return re.test(text) + } + + function uc(s) { + return s.toUpperCase(); + } + + function lc(s) { + return s.toLowerCase(); + } + + function capitalize(s) { + return uc(s.charAt(0)) + s.slice(1); + } + + function uncapitalize(s) { + return lc(s.charAt(0)) + s.slice(1); + } + + function removeInitialWhitespace(s) { + return s.replace(new RegExp("^ *", "i"), "") + } + + function getInitialWhitespace(s) { + return s.replace(/^( *).*/, "$1"); + } + + function maybe_prepend_name(s, name) { + var carry_on = false; + + plurifiable_verbs.forEach(function(verb) { + if (testre("^" + lc(verb) + "s", lc(s))) { + carry_on = true; + } + }) + + verbs.forEach(function(verb) { + if (testre("^" + lc(verb), lc(s))) { + carry_on = true; + } + }) + + if (carry_on) { + return name + " " + s; + } else { + return s; + } + } + + function setDifference(A, B) { + A.filter(function(x) { return B.indexOf(x) < 0 }) + } + + function firstWord(s) { + return s.split(" ")[0] + } + + function filterEmpty(arr) { + // ES6: subwords = subwords.filter(Boolean) + return arr.filter(function(v){return v!==''}); + } + + function splitCamel(s) { + return filterEmpty(s.split(/([A-Z][a-z]*)/)); + } + + // Makes an educated guess to whether or not the first word is an old function name + function smellsLikeOldName(first_word) { + // Ignore when there are special characters. Allow a couple of () though so it can be rewritten + if (testre("[^" + keywordchars + "()]", first_word)) { + return false; + } + + subwords = splitCamel(first_word); + name_subwords = splitCamel(name); + + // Ignore words without camel case, unless it is all upper case. + // Sometimes the 'new' first word is not on old function name + if ((subwords.length === 1) && ! (uc(subwords[0]) === subwords[0])) { + return false; + } + + same = setIntersection(subwords, name_subwords) + + var is_old_name = false; + + if (same.length > 0) { + return true; + } else { + //Split first word into camel-cased subwords + subwords.forEach(function(subword) { + if (subword.length < 4) return; + // If start of comment smells like an old name + if (lc(subword).indexOf(lc(name)) !== -1 || lc(name).indexOf(lc(subword)) !== -1) { + is_old_name = true; + } + }) + } + + return is_old_name; + } + + // Replaces the first word with a new function name + function updateOld(s, name) { + var words = s.split(" ") + words[0] = name; + return words.join(" ") + } + + // modal_verbs etc. + var verbs = ["will", "does", "is", "exist"]; + var plurifiable_verbs = ["split", "purge", "remove", "test", "ensure", "replace", "get", "start", + "validate", "execute", "demonstrate", "compare", "hook", + "report", "take", "clear", "handle", "decode", + "unregister", "capture", "retrieve", "declare", "check", + "return", "render", "add", "read", "write"]; + + + // If one of these tokens appear followed by an s, it should NOT be treated as a verb + var verb_plural_blacklist = ["lot", "alia", "arg", "resource", + "event", "GET"]; + + var nouns = ["test", "function", "method"]; + var adjectives = ["various", "exists"]; + var determiners = ["the", "this"]; + + function fixLeadingVerb(s) { + // Make these replacements before removing from the front + plurifiable_verbs.forEach(function(verb) { + if (! includes(verb_plural_blacklist, firstWord(s))) { + var pverb = verb + "s" + + s = s.replace(new RegExp("^" + verb + "s\? +", "i"), pverb + " ") + + adjectives.forEach(function(adjective) { + s = s.replace(new RegExp("^" + pverb + " " + adjective + " ", "i"), pverb + " " + lc(adjective) + " ") + }) + } + }) + + return s + } + + function includes(arr,obj) { + // As of ECMAScript 2016 you can use includes() + return (arr.indexOf(obj) != -1); + } + + function setUnion (x, y) { + var obj = {}; + for (var i = x.length-1; i >= 0; -- i) + obj[x[i]] = x[i]; + for (var i = y.length-1; i >= 0; -- i) + obj[y[i]] = y[i]; + var res = [] + for (var k in obj) { + if (obj.hasOwnProperty(k)) // <-- optional + res.push(obj[k]); + } + return res; + } + + function setIntersection(a, b) { + var ai=0, bi=0; + var result = []; + + while( ai < a.length && bi < b.length ) + { + if (a[ai] < b[bi] ){ ai++; } + else if (a[ai] > b[bi] ){ bi++; } + else /* they're equal */ + { + result.push(a[ai]); + ai++; + bi++; + } + } + + return result; + } + + function rewordComment(s, name, commentChar) { + // Removing 'The' is fine, unless it is replaced by something + // without a capital letter because that may be a common noun, such + // as 'distance', there the function comment is on a 'Distance' + // function. + s = s.replace(new RegExp("^(The) " + capitalize(name) + " "), "") + + // Make these replacements before removing from the front + s = fixLeadingVerb(s) + + first_word = firstWord(s); + + // Ignore parentheses + if (testre("^" + name + "\\(", first_word)) { + return s; + } + + if (smellsLikeOldName(first_word)) { + // could optionally fix verb here but don't + s = updateOld(s, name) + return s; + } + + s = fixLeadingVerb(s) + s = maybe_prepend_name(s, name) + + return s + } + + var commentChar = text.substring(0, 2) + var commentString = text.substring(2) + var whitespace = getInitialWhitespace(commentString) + + commentString = commentString.trim().replace(new RegExp("^[/*]* ", "i"), " ") // Remove extra leading comment chars + + commentString = removeInitialWhitespace(commentString).trim() + + // Do nothing if comment starts with + // - non-alphanumerics + // - test + // - LABEL: + // - TODO + if ( + (! testre("^[A-Za-z]", commentString)) || + (testre("^test", lc(commentString))) || + (testre("^[a-zA-Z]+:", commentString)) || + (testre("^TODO ", commentString)) || + (testre("^A-Za-z_", firstWord(commentString))) + ) { + + return text + } + + return commentChar + whitespace + rewordComment(commentString, name, commentChar) + } +tenets: + - name: update-comment-first-word-as-subject + actions: + codelingo/docs: + title: Update Comment First Word as Subject + body: | + Doc comments work best as complete sentences, which allow a wide variety of automated presentations. + The first sentence should be a summary that starts with the name being declared. + codelingo/review: + comment: | + Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name ({{funcName}}) being declared. + From [effective go](https://golang.org/doc/effective_go.html#commentary). + codelingo/rewrite: + place: holder + query: | + import codelingo/ast/go + + go.file(depth = any): + filename as filename + isNotATestFile(filename) + go.func_decl(depth = any): + go.comment_group: + @review comment + @rewrite --replace "{{fixComment(commText, funcName)}}" + go.comment: + sibling_order == 0 + text as commText + go.ident: + name as funcName + isExported(funcName) + isValid(commText, funcName) diff --git a/tenets/codelingo/effective-go/update-comment-first-word-as-subject/example.go b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/example.go new file mode 100644 index 00000000..6336406f --- /dev/null +++ b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/example.go @@ -0,0 +1,48 @@ +// Package main used for testing the tenet +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, playground") + // This comment not on a func decl +} + +/* foo is the first word + */ +func foo() {} + + +/* Foo is the first word + */ +func Foo() {} + +/* This func comment should begin with 'bar' + */ +func bar() {} + +/* This func comment should begin with 'Bar' + */ +func Bar() {} + +// This func comment should begin with 'baz' +// and we should not worry about this line +func baz() {} + +// This func comment should begin with 'Baz' +// and we should not worry about this line +func Baz() {} + +// This is called by a xyz +func qux() {} + +// This is called by a xyz +func Qux() {} + +// The quux will handle xyz +func quux() {} + +// The Quux will handle xyz +func Quux() {} \ No newline at end of file diff --git a/tenets/codelingo/effective-go/update-comment-first-word-as-subject/example_test.go b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/example_test.go new file mode 100644 index 00000000..6336406f --- /dev/null +++ b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/example_test.go @@ -0,0 +1,48 @@ +// Package main used for testing the tenet +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, playground") + // This comment not on a func decl +} + +/* foo is the first word + */ +func foo() {} + + +/* Foo is the first word + */ +func Foo() {} + +/* This func comment should begin with 'bar' + */ +func bar() {} + +/* This func comment should begin with 'Bar' + */ +func Bar() {} + +// This func comment should begin with 'baz' +// and we should not worry about this line +func baz() {} + +// This func comment should begin with 'Baz' +// and we should not worry about this line +func Baz() {} + +// This is called by a xyz +func qux() {} + +// This is called by a xyz +func Qux() {} + +// The quux will handle xyz +func quux() {} + +// The Quux will handle xyz +func Quux() {} \ No newline at end of file diff --git a/tenets/codelingo/effective-go/update-comment-first-word-as-subject/expected.json b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/expected.json new file mode 100644 index 00000000..27289e10 --- /dev/null +++ b/tenets/codelingo/effective-go/update-comment-first-word-as-subject/expected.json @@ -0,0 +1,26 @@ +[ + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Baz) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", + "Filename": "example.go", + "Line": 34, + "Snippet": "func baz() {}\n\n// This func comment should begin with 'Baz'\n// and we should not worry about this line\nfunc Baz() {}" + }, + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Quux) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", + "Filename": "example.go", + "Line": 47, + "Snippet": "func quux() {}\n\n// The Quux will handle xyz\nfunc Quux() {}" + }, + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Qux) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", + "Filename": "example.go", + "Line": 41, + "Snippet": "func qux() {}\n\n// This is called by a xyz\nfunc Qux() {}\n" + }, + { + "Comment": "Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name (Bar) being declared.\nFrom [effective go](https://golang.org/doc/effective_go.html#commentary).\n", + "Filename": "example.go", + "Line": 26, + "Snippet": "func bar() {}\n\n/* This func comment should begin with 'Bar'\n */\nfunc Bar() {}\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/README.md b/tenets/codelingo/go/README.md index f1f616a1..81151818 100644 --- a/tenets/codelingo/go/README.md +++ b/tenets/codelingo/go/README.md @@ -1,5 +1,5 @@ # Go -Useful tenets for your Go repository. +Essential tenets for your Go repository. ### Ideas diff --git a/tenets/codelingo/go/avoid-delayed-must-calls/codelingo.yaml b/tenets/codelingo/go/avoid-delayed-must-calls/codelingo.yaml new file mode 100644 index 00000000..273a0c17 --- /dev/null +++ b/tenets/codelingo/go/avoid-delayed-must-calls/codelingo.yaml @@ -0,0 +1,25 @@ +funcs: + - name: isNotValidFunc + type: asserter + body: | + function (name) { + return name !== "init" && name !== "main" + } +tenets: + - name: avoid-delayed-must-calls + actions: + codelingo/review: + comment: Functons prefixed with Must generally panic instead of returning errors. Only use in globals or init/main functions. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.ident: + name as func + isNotValidFunc(func) + @review comment + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name as funcCall + regex(/^Must([A-Z]|$)/, funcCall) diff --git a/tenets/codelingo/go/avoid-delayed-must-calls/example.go b/tenets/codelingo/go/avoid-delayed-must-calls/example.go new file mode 100644 index 00000000..f9532f40 --- /dev/null +++ b/tenets/codelingo/go/avoid-delayed-must-calls/example.go @@ -0,0 +1,60 @@ +package main + +import ( + "regexp" + "fmt" +) + +func main() { + foo() + bar() + + red := regexp.MustCompile("a") + fmt.Println(red.String()) +} + +func foo() { + + var green, _ = regexp.Compile("a") + + yellow, _ := regexp.Compile("a") + + red := regexp.MustCompile("a") // Issue + + fmt.Printf("%s %s %s \n", green.String(), yellow.String(), red.String()) +} + +func bar() { + var green, _ = regexp.Compile("a") + + yellow, _ := regexp.Compile("a") + + red := regexp.MustCompile("a") // Issue + + indigo := regexp.MustCompile("a").Match([]byte(`seafood`)) // Issue + + if indigo == true { + fmt.Printf("%s %s %s \n", green.String(), yellow.String(), red.String()) + } +} + +func baz() { + matched := regexp.MustCompile("a").MatchString("b") // Issue + if matched { + fmt.Println(matched) + } + + if regexp.MustCompile("a").MatchString("c") { // Issue + fmt.Println(matched) + } +} + +func init() { + var green, _ = regexp.Compile("a") + + yellow, _ := regexp.Compile("a") + + red := regexp.MustCompile("a") // Acceptable + + fmt.Printf(green.String() + yellow.String() + red.String()) +} diff --git a/tenets/codelingo/go/avoid-delayed-must-calls/expected.json b/tenets/codelingo/go/avoid-delayed-must-calls/expected.json new file mode 100755 index 00000000..9050b270 --- /dev/null +++ b/tenets/codelingo/go/avoid-delayed-must-calls/expected.json @@ -0,0 +1,32 @@ +[ + { + "Comment": "Functons prefixed with Must generally panic instead of returning errors. Only use in globals or init/main functions.", + "Filename": "example.go", + "Line": 47, + "Snippet": "\t}\n\n\tif regexp.MustCompile(\"a\").MatchString(\"c\") { // Issue\n\t\tfmt.Println(matched)\n\t}" + }, + { + "Comment": "Functons prefixed with Must generally panic instead of returning errors. Only use in globals or init/main functions.", + "Filename": "example.go", + "Line": 42, + "Snippet": "\nfunc baz() {\n\tmatched := regexp.MustCompile(\"a\").MatchString(\"b\") // Issue\n\tif matched {\n\t\tfmt.Println(matched)" + }, + { + "Comment": "Functons prefixed with Must generally panic instead of returning errors. Only use in globals or init/main functions.", + "Filename": "example.go", + "Line": 32, + "Snippet": "\tyellow, _ := regexp.Compile(\"a\")\n\n\tred := regexp.MustCompile(\"a\") // Issue\n\n\tindigo := regexp.MustCompile(\"a\").Match([]byte(`seafood`)) // Issue" + }, + { + "Comment": "Functons prefixed with Must generally panic instead of returning errors. Only use in globals or init/main functions.", + "Filename": "example.go", + "Line": 34, + "Snippet": "\tred := regexp.MustCompile(\"a\") // Issue\n\n\tindigo := regexp.MustCompile(\"a\").Match([]byte(`seafood`)) // Issue\n\n\tif indigo == true {" + }, + { + "Comment": "Functons prefixed with Must generally panic instead of returning errors. Only use in globals or init/main functions.", + "Filename": "example.go", + "Line": 22, + "Snippet": "\tyellow, _ := regexp.Compile(\"a\")\n\n\tred := regexp.MustCompile(\"a\") // Issue\n\n\tfmt.Printf(\"%s %s %s \\n\", green.String(), yellow.String(), red.String())" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/bool-param/codelingo.yaml b/tenets/codelingo/go/bool-param/codelingo.yaml index 2da5c193..01aea21e 100644 --- a/tenets/codelingo/go/bool-param/codelingo.yaml +++ b/tenets/codelingo/go/bool-param/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: bool-arg - flows: + actions: codelingo/docs: title: Bool Arg body: | diff --git a/tenets/codelingo/go/break-select-in-for/codelingo.yaml b/tenets/codelingo/go/break-select-in-for/codelingo.yaml new file mode 100644 index 00000000..65a210d9 --- /dev/null +++ b/tenets/codelingo/go/break-select-in-for/codelingo.yaml @@ -0,0 +1,19 @@ +tenets: + - name: break-select-in-for + flows: + codelingo/review: + comment: | + This `break` statement only breaks to the innermost select, rather than the parent `for` loop. + This is generally unexpected and can cause an active goroutine to leak. + Fix it by adding a label to the for loop and breaking to that instead. + query: | + import codelingo/ast/go + + @review comment + go.for_stmt(depth = any): + go.select_stmt(depth = any): + go.branch_stmt(depth = any): + tok == "break" + exclude: + go.ident + diff --git a/tenets/codelingo/go/break-select-in-for/example.go b/tenets/codelingo/go/break-select-in-for/example.go new file mode 100644 index 00000000..e7cba2ec --- /dev/null +++ b/tenets/codelingo/go/break-select-in-for/example.go @@ -0,0 +1,85 @@ +package main + +import "fmt" + +func main() { + // We generally write break statements to break out of repetitious things like for loops + fmt.Println("Expecting 0, 1:") + for i := range []int{0, 1, 2} { + fmt.Println(i) + if i == 1 { + break // Non-issue + } + } + + // and don't often break from selects, so it's easy to forget that it's possible + fmt.Println("Expecting 0:") + c1 := intChan() + select { + case i := <-c1: + fmt.Println(i) + break // Non-issue + } + + // We understand that breaking from nested loops only exits the innermost + fmt.Println("Expecting {0 ,0}, {1, 0}, {2, 0}:") + for i := range []int{0, 1, 2} { + for j := range []int{0, 1, 2} { + fmt.Println(i, j) + if j == 0 { + break // Non-issue + } + } + } + + // Unless, of course, we use a label + fmt.Println("Expecting {0 ,0}") +l: + for i := range []int{0, 1, 2} { + for j := range []int{0, 1, 2} { + fmt.Println(i, j) + if j == 0 { + break l // Non-issue + } + } + } + + // Which works perfectly fine to break a select inside a for loop + fmt.Println("Expecting 0, 1, 2:") + c2 := intChan() +m: + for { + select { + case i, ok := <-c2: + fmt.Println(i) + if !ok { + fmt.Println("But actually we get infinite 0s as well") + break m // Non-issue + } + } + } + + // The trouble is, we often use selects inside for loops and intend to break the for loop + // forgetting that we need a label lest we only break the select statement + // This leads to infinite 0s after our expected 0, 1, 2 + fmt.Println("Expecting 0, 1, 2:") + c3 := intChan() + for { + select { + case i, ok := <-c3: + fmt.Println(i) + if !ok { + break // ISSUE + } + } + } +} + +func intChan() <-chan int { + ch := make(chan int, 3) + ch <- 1 + ch <- 2 + ch <- 3 + close(ch) + return ch +} diff --git a/tenets/codelingo/go/check-reader-bytes-before-err/codelingo.yaml b/tenets/codelingo/go/check-reader-bytes-before-err/codelingo.yaml new file mode 100644 index 00000000..de18d39d --- /dev/null +++ b/tenets/codelingo/go/check-reader-bytes-before-err/codelingo.yaml @@ -0,0 +1,53 @@ +funcs: + - name: compareFunc + type: asserter + body: | + function (a, b) { + return a < b; + } + - name: threeComparison + type : asserter + body: | + function (a, b, c){ + return (a < b) && (a > c); + } +tenets: + - name: check-reader-bytes-before-err + actions: + codelingo/review: + comment: Check io.Reader bytes read (and process) before checking err + query: | + import codelingo/ast/go + + go.block_stmt(depth = any): + go.list: + go.assign_stmt: + sibling_order as so1 + go.lhs: + go.ident: + name == "err" + go.rhs: + go.call_expr: + go.selector_expr: + go.ident: + name == "Read" + go.args: + go.ident: + name as x + @review comment + go.if_stmt: + sibling_order as so2 + compareFunc(so1, so2) + go.binary_expr: + go.ident: + name == "err" + exclude: + go.expr_stmt: + sibling_order as so3 + threeComparison(so3, so2, so1) + go.call_expr: + go.args: + go.call_expr: + go.args: + go.ident: + name == x \ No newline at end of file diff --git a/tenets/codelingo/go/check-reader-bytes-before-err/example.go b/tenets/codelingo/go/check-reader-bytes-before-err/example.go new file mode 100644 index 00000000..9f95358a --- /dev/null +++ b/tenets/codelingo/go/check-reader-bytes-before-err/example.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + file, err := os.Open("test.txt") + if err != nil { + panic(err) + } + defer file.Close() + + buff := make([]byte, 6) + + n, err := file.Read(buff) + fmt.Println("read", n, "data:", string(buff)) + if err != nil { + panic(err) + } + + n, err = file.Read(buff) + if err != nil { // Issue + panic(err) + } + + fmt.Println("read", n, "data:", string(buff)) + +} diff --git a/tenets/codelingo/go/check-reader-bytes-before-err/expected.json b/tenets/codelingo/go/check-reader-bytes-before-err/expected.json new file mode 100644 index 00000000..67f1457a --- /dev/null +++ b/tenets/codelingo/go/check-reader-bytes-before-err/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Check io.Reader bytes read (and process) before checking err", + "Filename": "example.go", + "Line": 24, + "Snippet": "\n\tn, err = file.Read(buff)\n\tif err != nil { // Issue\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"read\", n, \"data:\", string(buff))" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/concurrent-use-of-rand/codelingo.yaml b/tenets/codelingo/go/concurrent-use-of-rand/codelingo.yaml new file mode 100644 index 00000000..08698571 --- /dev/null +++ b/tenets/codelingo/go/concurrent-use-of-rand/codelingo.yaml @@ -0,0 +1,50 @@ +tenets: + - name: concurrent-use-of-rand + actions: + codelingo/review: + comment: Unlike the default source used by top-level functions in math.rand package, a new source is not safe for concurrent use. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + exclude: + go.go_stmt(depth = any): + include: + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name as source + go.rhs: + go.call_expr: + go.selector_expr: + go.ident: + name == "rand" + go.ident: + name == "New" + go.args: + go.call_expr: + go.selector_expr: + go.ident: + name == "rand" + go.ident: + name == "NewSource" + go.decl_stmt(depth = any): + go.value_spec(depth = any): + go.names: + go.ident: + name as mutex + go.selector_expr: + go.ident: + name == "sync" + go.ident: + name == "Mutex" + go.go_stmt(depth = any): + @review comment + go.ident(depth = any): + name == source + exclude: + go.selector_expr(depth = any): + go.ident: + name == "mu" + go.ident: + name == "Unlock" diff --git a/tenets/codelingo/go/concurrent-use-of-rand/example.go b/tenets/codelingo/go/concurrent-use-of-rand/example.go new file mode 100644 index 00000000..2c08fe94 --- /dev/null +++ b/tenets/codelingo/go/concurrent-use-of-rand/example.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "math/rand" + "sync" +) + +func main() { + + source := rand.New(rand.NewSource(43)) + + var mu sync.Mutex + + go func(r *rand.Rand) { + fmt.Println(r.Int31()) + }(source) // Issue + + go func() { + safeSource := rand.New(rand.NewSource(11)) + fmt.Println(safeSource.Int31()) + }() + + go func() { + fmt.Println(source.Int31()) //Issue + }() + + go func(r *rand.Rand) { + + mu.Lock() + fmt.Println(r.Int31()) + mu.Unlock() + + }(source) + + printRand(source) + + go printRand(source) // Issue + + printRandUniqueSource() + + go printRandUniqueSource() + + sourceTwo := getSource(31) + fmt.Println(sourceTwo.Int31()) + + printRand(sourceTwo) + + go printRand(sourceTwo) // Issue (Requires use of CLQL types to catch) + + go func(r *rand.Rand) { + + mu.Lock() + fmt.Println(r.Int31()) + mu.Unlock() + }(sourceTwo) // Issue (Requires callgraph to identify sourceTwo as it is returned by an function) +} + +func printRand(r *rand.Rand) { + + fmt.Println(r.Int31()) +} + +func printRandUniqueSource() { + + source := rand.New(rand.NewSource(11)) + fmt.Println(source.Int31()) +} + +func getSource(seed int64) (*rand.Rand) { + + return rand.New(rand.NewSource(seed)) +} diff --git a/tenets/codelingo/go/concurrent-use-of-rand/expected.json b/tenets/codelingo/go/concurrent-use-of-rand/expected.json new file mode 100755 index 00000000..3b413e53 --- /dev/null +++ b/tenets/codelingo/go/concurrent-use-of-rand/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "Unlike the default source used by top-level functions in math.rand package, a new source is not safe for concurrent use.", + "Filename": "example.go", + "Line": 37, + "Snippet": "\tprintRand(source)\n\n\tgo printRand(source) // Issue\n\n\tprintRandUniqueSource()" + }, + { + "Comment": "Unlike the default source used by top-level functions in math.rand package, a new source is not safe for concurrent use.", + "Filename": "example.go", + "Line": 25, + "Snippet": "\n\tgo func() {\n\t\tfmt.Println(source.Int31()) //Issue\n\t}()\n" + }, + { + "Comment": "Unlike the default source used by top-level functions in math.rand package, a new source is not safe for concurrent use.", + "Filename": "example.go", + "Line": 17, + "Snippet": "\tgo func(r *rand.Rand) {\n\t\tfmt.Println(r.Int31())\n\t}(source) // Issue\n\n\tgo func() {" + } + ] diff --git a/tenets/codelingo/go/counter-not-incremented/codelingo.yaml b/tenets/codelingo/go/counter-not-incremented/codelingo.yaml new file mode 100644 index 00000000..0c434941 --- /dev/null +++ b/tenets/codelingo/go/counter-not-incremented/codelingo.yaml @@ -0,0 +1,59 @@ +tenets: + - name: counter-not-incremented + actions: + codelingo/review: + comment: This for loop does not increment or decrement the counter and will cause an infinite loop. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + any_of: + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name as counterName + go.rhs: + any_of: + go.basic_lit: + kind == "int" + go.basic_lit: + kind == "float" + go.decl_stmt(depth = any): + go.gen_decl: + go.value_spec: + go.names: + go.ident: + name as counterName + any_of: + go.ident: + name == "int" + go.ident: + name == "int8" + go.ident: + name == "int16" + go.ident: + name == "int32" + go.ident: + name == "int64" + go.ident: + name == "uint" + go.ident: + name == "uint8" + go.ident: + name == "uint16" + go.ident: + name == "uint32" + go.ident: + name == "uint64" + go.ident: + name == "float32" + go.ident: + name == "float64" + @review comment + go.for_stmt(depth = any): + go.binary_expr: + go.ident: + name == counterName + exclude: + go.inc_dec_stmt(depth = any): + name == counterName diff --git a/tenets/codelingo/go/counter-not-incremented/example.go b/tenets/codelingo/go/counter-not-incremented/example.go new file mode 100644 index 00000000..9b2dbca4 --- /dev/null +++ b/tenets/codelingo/go/counter-not-incremented/example.go @@ -0,0 +1,97 @@ +package main + +import "fmt" + +func main() { + ints() + floats() +} + +func ints() { + + a := 3 + for a < 10 { // Issue + fmt.Println(a) + } + + b := 4 + for b < 5 { + fmt.Println(b) + b++ + } + + for c := 2; c < 8; c++ { + fmt.Println(c) + } + + d := 5 + for 6 > d { // Issue + fmt.Println(d) + } + + e := 7 + for e > 2 { + fmt.Println(e) + e-- + } + + for f := 6; f > 1; f-- { + fmt.Println(f) + } + + var g int + g = 3 + for g < 8 { // Issue + fmt.Println(g) + } + + var h int = 2 + for h < 9 { // Issue + fmt.Println(h) + } + + var j int8 = 3 + for j < 8 { // Issue + fmt.Println(j) + } + + var k int32 + k = 2 + for k < 7 { // Issue + fmt.Println(k) + } + + var l uint = 2 + for l < 9 { // Issue + fmt.Println(l) + } +} + +func floats() { + + pi := 3.1415 + for pi < 6 { // Issue + fmt.Println(pi) + } + + e := 2.7182 + for e < 6 { + fmt.Println(e) + e++ + } + + for a := 2.2; a < 5; a++ { + fmt.Println(a) + } + + var b float32 = 3.3 + for b < 8 { // Issue + fmt.Println(b) + } + + var c float64 = 4.4 + for c < 7 { + fmt.Println(c) + c++ + } +} diff --git a/tenets/codelingo/go/counter-not-incremented/expected.json b/tenets/codelingo/go/counter-not-incremented/expected.json new file mode 100755 index 00000000..c3700057 --- /dev/null +++ b/tenets/codelingo/go/counter-not-incremented/expected.json @@ -0,0 +1,56 @@ +[ + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 65, + "Snippet": "\n\tvar l uint = 2\n\tfor l \u003c 9 { // Issue\n\t\tfmt.Println(l)\n\t}\n}\n" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 28, + "Snippet": "\n\td := 5\n\tfor 6 \u003e d { // Issue\n\t\tfmt.Println(d)\n\t}\n\n\te := 7" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 54, + "Snippet": "\n\tvar j int8 = 3\n\tfor j \u003c 8 { // Issue\n\t\tfmt.Println(j)\n\t}\n\n\tvar k int32" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 44, + "Snippet": "\tvar g int\n\tg = 3\n\tfor g \u003c 8 { // Issue\n\t\tfmt.Println(g)\n\t}\n\n\tvar h int = 2" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 13, + "Snippet": "\n\ta := 3\n\tfor a \u003c 10 { // Issue\n\t\tfmt.Println(a)\n\t}\n\n\tb := 4" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 49, + "Snippet": "\n\tvar h int = 2\n\tfor h \u003c 9 { // Issue\n\t\tfmt.Println(h)\n\t}\n\n\tvar j int8 = 3" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 60, + "Snippet": "\tvar k int32\n\tk = 2\n\tfor k \u003c 7 { // Issue\n\t\tfmt.Println(k)\n\t}\n\n\tvar l uint = 2" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 73, + "Snippet": "\n\tpi := 3.1415\n\tfor pi \u003c 6 { // Issue\n\t\tfmt.Println(pi)\n\t}\n\n\te := 2.7182" + }, + { + "Comment": "This for loop does not increment or decrement the counter and will cause an infinite loop.", + "Filename": "example.go", + "Line": 88, + "Snippet": "\n\tvar b float32 = 3.3\n\tfor b \u003c 8 { // Issue\n\t\tfmt.Println(b)\n\t}\n\n\tvar c float64 = 4.4" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/default-in-type-switch/codelingo.yaml b/tenets/codelingo/go/default-in-type-switch/codelingo.yaml new file mode 100644 index 00000000..008521b8 --- /dev/null +++ b/tenets/codelingo/go/default-in-type-switch/codelingo.yaml @@ -0,0 +1,15 @@ +tenets: + - name: default-in-type-switch + actions: + codelingo/review: + comment: Always include a default case when using a type switch. + query: | + import codelingo/ast/go + + @review comment + go.type_switch_stmt(depth = any): + exclude: + go.case_clause(depth = any): + go.body + exclude: + go.list diff --git a/tenets/codelingo/go/default-in-type-switch/expected.json b/tenets/codelingo/go/default-in-type-switch/expected.json new file mode 100755 index 00000000..39cdad33 --- /dev/null +++ b/tenets/codelingo/go/default-in-type-switch/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Always include a default case when using a type switch.", + "Filename": "main.go", + "Line": 18, + "Snippet": "\nfunc dont(i interface{}) {\n\tswitch v := i.(type) { // ISSUE\n\tcase int:\n\t\tfmt.Printf(\"Twice %v is %v\\n\", v, v*2)\n\tcase string:\n\t\tfmt.Printf(\"%q is %v bytes long\\n\", v, len(v))\n\t}\n\n}" + } + ] diff --git a/tenets/codelingo/go/default-in-type-switch/main.go b/tenets/codelingo/go/default-in-type-switch/main.go new file mode 100644 index 00000000..cfde403d --- /dev/null +++ b/tenets/codelingo/go/default-in-type-switch/main.go @@ -0,0 +1,36 @@ +package main + +import "fmt" + +func do(i interface{}) { + switch v := i.(type) { // Non-issue + case int: + fmt.Printf("Twice %v is %v\n", v, v*2) + case string: + fmt.Printf("%q is %v bytes long\n", v, len(v)) + default: + fmt.Printf("I don't know about type %T!\n", v) + } + +} + +func dont(i interface{}) { + switch v := i.(type) { // ISSUE + case int: + fmt.Printf("Twice %v is %v\n", v, v*2) + case string: + fmt.Printf("%q is %v bytes long\n", v, len(v)) + } + +} + +func main() { + do(21) + do("hello") + do(true) + + dont(21) + dont("hello") + dont(true) +} + diff --git a/tenets/codelingo/go/deprecated/codelingo.yaml b/tenets/codelingo/go/deprecated/codelingo.yaml new file mode 100644 index 00000000..fb3f07e9 --- /dev/null +++ b/tenets/codelingo/go/deprecated/codelingo.yaml @@ -0,0 +1,18 @@ +tenets: + - name: deprecated + actions: + codelingo/review: + comment: This is a call to a [deprecated function](https://rakyll.org/deprecated/). + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.comment_group: + go.comment: + text as text + regex(/^\/\/ Deprecated: .*/, text) + # TODO: traverse dependencies + edge("called_by"): + @review comment + # Workaround to avoid implicit siblings due to equal depth https://github.com/codelingo/platform/issues/1410 + go.call_expr(depth = 0:2) diff --git a/tenets/codelingo/go/deprecated/example.go b/tenets/codelingo/go/deprecated/example.go new file mode 100644 index 00000000..2010bfce --- /dev/null +++ b/tenets/codelingo/go/deprecated/example.go @@ -0,0 +1,30 @@ +package main + +func main() { + deprecated() // ISSUE + deprecatedAndCommented() // ISSUE + notQuiteDeprecated() + badlyDeprecated() + loudlyDeprecated() + notDeprecated() +} + +// Deprecated: some reason +func deprecated() {} + +// This is an empty function +// +// Deprecated: some reason +func deprecatedAndCommented() {} + +/// Deprecated: some reason +func notQuiteDeprecated() {} + +// This type is deprecated for some reason +func badlyDeprecated() {} + +// DEPRECATED: some reason +func loudlyDeprecated() {} + +// This function is not deprecated +func notDeprecated() {} diff --git a/tenets/codelingo/go/deprecated/expected.json b/tenets/codelingo/go/deprecated/expected.json new file mode 100755 index 00000000..d3180990 --- /dev/null +++ b/tenets/codelingo/go/deprecated/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "This is a call to a deprecated function.", + "Filename": "example.go", + "Line": 4, + "Snippet": "\nfunc main() {\n\tdeprecated() // ISSUE\n\tdeprecatedAndCommented() // ISSUE\n\tnotQuiteDeprecated()" + }, + { + "Comment": "This is a call to a deprecated function.", + "Filename": "example.go", + "Line": 5, + "Snippet": "func main() {\n\tdeprecated() // ISSUE\n\tdeprecatedAndCommented() // ISSUE\n\tnotQuiteDeprecated()\n\tbadlyDeprecated()" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/empty-slice/codelingo.yaml b/tenets/codelingo/go/empty-slice/codelingo.yaml index 62039d2d..11ea33d4 100644 --- a/tenets/codelingo/go/empty-slice/codelingo.yaml +++ b/tenets/codelingo/go/empty-slice/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: empty-slice - flows: + actions: codelingo/rewrite: codelingo/docs: title: Empty Slice diff --git a/tenets/codelingo/go/global-var/codelingo.yaml b/tenets/codelingo/go/global-var/codelingo.yaml index 506bdf17..f81b30dc 100644 --- a/tenets/codelingo/go/global-var/codelingo.yaml +++ b/tenets/codelingo/go/global-var/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: global-variable - flows: + actions: codelingo/docs: title: Global Variable body: | diff --git a/tenets/codelingo/go/golint/codelingo.yaml b/tenets/codelingo/go/golint/codelingo.yaml index 73c49ee9..841936de 100644 --- a/tenets/codelingo/go/golint/codelingo.yaml +++ b/tenets/codelingo/go/golint/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: go-lint-rules - flows: + actions: codelingo/docs: title: Go Lint Rules body: | diff --git a/tenets/codelingo/go/goto/codelingo.yaml b/tenets/codelingo/go/goto/codelingo.yaml index c99d2f89..8a182a9e 100644 --- a/tenets/codelingo/go/goto/codelingo.yaml +++ b/tenets/codelingo/go/goto/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: goto-statement - flows: + actions: codelingo/docs: title: Goto Statement body: | diff --git a/tenets/codelingo/go/ignore-defered-returns/codelingo.yaml b/tenets/codelingo/go/ignore-defered-returns/codelingo.yaml new file mode 100644 index 00000000..660e42de --- /dev/null +++ b/tenets/codelingo/go/ignore-defered-returns/codelingo.yaml @@ -0,0 +1,21 @@ +tenets: + - name: ignore-defered-returns + actions: + codelingo/review: + comment: Values returned from defered functions should not be ignored. + query: | + import codelingo/ast/go + + @review comment + go.defer_stmt(depth = any): + go.call_expr: + any_of: + edge("calls"): + go.func_decl: + go.func_type: + go.field_list + go.field_list + go.func_lit: + go.func_type: + go.field_list + go.field_list diff --git a/tenets/codelingo/go/ignore-defered-returns/example.go b/tenets/codelingo/go/ignore-defered-returns/example.go new file mode 100644 index 00000000..bf23e5b2 --- /dev/null +++ b/tenets/codelingo/go/ignore-defered-returns/example.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" +) + +func doStuff() string { + return "Things" +} + +func sayStuff() { + fmt.Println("Things") +} + + +func main() { + + defer doStuff() // Issue + defer sayStuff() // Non Issue + + defer func() { // Non Issue + fmt.Println("Hello") + }() + + defer func() string { // Issue + return "Hello" + }() +} diff --git a/tenets/codelingo/go/ignore-defered-returns/expected.json b/tenets/codelingo/go/ignore-defered-returns/expected.json new file mode 100755 index 00000000..5639b30d --- /dev/null +++ b/tenets/codelingo/go/ignore-defered-returns/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Values returned from defered functions should not be ignored.", + "Filename": "example.go", + "Line": 18, + "Snippet": "func main() {\n\n\tdefer doStuff() // Issue\n\tdefer sayStuff() // Non Issue\n" + }, + { + "Comment": "Values returned from defered functions should not be ignored.", + "Filename": "example.go", + "Line": 25, + "Snippet": "\t}()\n\n\tdefer func() string { // Issue\n\t\treturn \"Hello\"\n\t}()\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/ignore-goroutine-return-values/codelingo.yaml b/tenets/codelingo/go/ignore-goroutine-return-values/codelingo.yaml new file mode 100644 index 00000000..a45a2342 --- /dev/null +++ b/tenets/codelingo/go/ignore-goroutine-return-values/codelingo.yaml @@ -0,0 +1,22 @@ +tenets: + - name: dont-ignore-gofunc-returns + actions: + codelingo/review: + comment: Do not ignore values returned by goroutines. + query: | + import codelingo/ast/go + + @review comment + go.go_stmt(depth = any): + go.call_expr: + any_of: + go.func_lit: + go.func_type: + go.field_list + go.field_list + edge("calls"): + go.func_decl: + go.func_type: + go.field_list + go.field_list + diff --git a/tenets/codelingo/go/ignore-goroutine-return-values/example.go b/tenets/codelingo/go/ignore-goroutine-return-values/example.go new file mode 100644 index 00000000..f689f4ca --- /dev/null +++ b/tenets/codelingo/go/ignore-goroutine-return-values/example.go @@ -0,0 +1,33 @@ +package main + +import ( + "time" + "fmt" +) + +func main() { + go A() // Issue + go B() // Non Issue + <-time.After(time.Second) + + go func() string { // Issue + word := "Hello" + return word + }() + + + + + go func() { // Non Issue + fmt.Println("Hello") + }() + +} + +func A () string { + return "str" +} + +func B () { + fmt.Println("Hey") +} diff --git a/tenets/codelingo/go/ignore-goroutine-return-values/expected.json b/tenets/codelingo/go/ignore-goroutine-return-values/expected.json new file mode 100755 index 00000000..6158af40 --- /dev/null +++ b/tenets/codelingo/go/ignore-goroutine-return-values/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Do not ignore values returned by go-funcs.", + "Filename": "example.go", + "Line": 13, + "Snippet": "\t\u003c-time.After(time.Second)\n\n\tgo func() string {\n\t word := \"Hello\"\n\t return word // Issue\n }()\n\n" + }, + { + "Comment": "Do not ignore values returned by go-funcs.", + "Filename": "example.go", + "Line": 9, + "Snippet": "\nfunc main() {\n\tgo A() // Issue\n go B() // Non Issue\n\t\u003c-time.After(time.Second)" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/init/codelingo.yaml b/tenets/codelingo/go/init/codelingo.yaml index 25fb1f42..415be879 100644 --- a/tenets/codelingo/go/init/codelingo.yaml +++ b/tenets/codelingo/go/init/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: init - flows: + actions: codelingo/docs: title: Init body: | diff --git a/tenets/codelingo/go/insecure-http-connection/codelingo.yaml b/tenets/codelingo/go/insecure-http-connection/codelingo.yaml new file mode 100644 index 00000000..888b13b5 --- /dev/null +++ b/tenets/codelingo/go/insecure-http-connection/codelingo.yaml @@ -0,0 +1,58 @@ +tenets: + - name: insecure-http-connection + actions: + codelingo/review: + comment: Skipping verification exposes the connection to man-in-the-middle attacks. This should only be used for testing. + query: | + import codelingo/ast/go + + go.file(depth = any): + any_of: + @review comment + go.key_value_expr(depth = any): + go.ident + go.unary_expr: + go.composite_lit: + go.selector_expr: + go.ident: + name == "tls" + go.ident: + name == "Config" + go.elts: + go.key_value_expr: + go.ident: + name == "InsecureSkipVerify" + go.ident: + name == "true" + @review comment + go.assign_stmt(depth = any): + go.rhs: + go.unary_expr: + go.composite_lit: + go.selector_expr(depth = any): + go.ident: + name == "tls" + go.ident: + name == "Config" + go.elts: + go.key_value_expr: + go.ident: + name == "InsecureSkipVerify" + go.ident: + name == "true" + go.func_decl(depth = any): + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name as var + @review comment + go.assign_stmt(depth = any): + go.lhs: + go.selector_expr: + go.ident: + name == var + go.ident: + name == "InsecureSkipVerify" + go.rhs: + go.ident: + name == "true" diff --git a/tenets/codelingo/go/insecure-http-connection/example.go b/tenets/codelingo/go/insecure-http-connection/example.go new file mode 100644 index 00000000..42a82ca3 --- /dev/null +++ b/tenets/codelingo/go/insecure-http-connection/example.go @@ -0,0 +1,75 @@ +package main + +import ( + "crypto/tls" + "log" + "net/http" + "net/http/httptest" + "os" +) + +type zeroSource struct {} + +func (zeroSource) Read(b []byte) (n int, err error) { + for i := range b { + b[i] = 0 + } + + return len(b), nil +} + +func main() { + + serverOne := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + serverOne.TLS = &tls.Config{ + Rand: zeroSource{}, + } + + serverOne.StartTLS() + defer serverOne.Close() + + serverTwo := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + serverTwo.TLS = &tls.Config{ // Issue + Rand: zeroSource{}, + InsecureSkipVerify: true, + } + + serverTwo.StartTLS() + defer serverTwo.Close() + + w := os.Stdout + + clientOne := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + KeyLogWriter: w, + Rand: zeroSource{}, + }, + }, + } + resp, err := clientOne.Get(serverOne.URL) + if err != nil { + log.Fatalf("Failed to get url: %v", err) + } + + resp.Body.Close() + + clientTwo := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ // Issue + KeyLogWriter: w, + Rand: zeroSource{}, + InsecureSkipVerify: true, + }, + }, + } + resp, err = clientTwo.Get(serverTwo.URL) + if err != nil { + log.Fatalf("Failed to get url: %v", err) + } + + resp.Body.Close() + + foo := &tls.Config{} + foo.InsecureSkipVerify = true // Issue +} diff --git a/tenets/codelingo/go/insecure-http-connection/expected.json b/tenets/codelingo/go/insecure-http-connection/expected.json new file mode 100755 index 00000000..5461e740 --- /dev/null +++ b/tenets/codelingo/go/insecure-http-connection/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "Skipping verification exposes the connection to man-in-the-middle attacks. This should only be used for testing.", + "Filename": "example.go", + "Line": 59, + "Snippet": "\tclientTwo := http.Client{\n\t\tTransport: \u0026http.Transport{\n\t\t\tTLSClientConfig: \u0026tls.Config{ // Issue\n\t\t\t\tKeyLogWriter: w,\n\t\t\t\tRand: zeroSource{},\n\t\t\t\tInsecureSkipVerify: true,\n\t\t\t},\n\t\t},\n\t}" + }, + { + "Comment": "Skipping verification exposes the connection to man-in-the-middle attacks. This should only be used for testing.", + "Filename": "example.go", + "Line": 32, + "Snippet": "\n\tserverTwo := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))\n\tserverTwo.TLS = \u0026tls.Config{ // Issue\n\t\tRand: zeroSource{},\n\t\tInsecureSkipVerify: true,\n\t}\n\n\tserverTwo.StartTLS()" + }, + { + "Comment": "Skipping verification exposes the connection to man-in-the-middle attacks. This should only be used for testing.", + "Filename": "example.go", + "Line": 74, + "Snippet": "\n\tfoo := \u0026tls.Config{}\n\tfoo.InsecureSkipVerify = true // Issue\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/lingo_bundle.yaml b/tenets/codelingo/go/lingo_bundle.yaml index d77005a4..11ce6cc1 100644 --- a/tenets/codelingo/go/lingo_bundle.yaml +++ b/tenets/codelingo/go/lingo_bundle.yaml @@ -1,20 +1,42 @@ - description: Best Practices for Golang. version: 0.0.0 tenets: - - bool-param - - empty-slice - - global-var - - golint - - goto - - marshalling - - nil-only-functions - - println-format-strings - - reallocated-slice - - sprintf - - tested - - todo - - unconvert +- avoid-delayed-must-calls +- bool-param +- break-select-in-for +- check-reader-bytes-before-err +- default-in-type-switch +- deprecated +- empty-slice +- global-var +- golint +- goto +- init +- marshalling +- nil-only-functions +- non-directional-chan +- non-exiting-goroutine +- pointer-leaves-method +- println-format-strings +- reallocated-slice +- reassigned-error +- remove-break-statement +- remove-empty-suite-functions +- shadowed-variable +- shared-count-datarace +- sprintf +- surprising-for-direction +- tested +- ticker-in-for-select +- todo +- unchecked-type-assertion +- unconvert +- unnecessary-parentheses +- unsafe-go-routine-variables +- unused-private-functions +- insecure-http-connection +- concurrent-use-of-rand +- counter-not-incremented tags: - - golang - - go +- golang +- go diff --git a/tenets/codelingo/go/marshalling/codelingo.yaml b/tenets/codelingo/go/marshalling/codelingo.yaml index f52cbf2e..0f41cb72 100644 --- a/tenets/codelingo/go/marshalling/codelingo.yaml +++ b/tenets/codelingo/go/marshalling/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: mixed-marshalling - flows: + actions: codelingo/docs: title: Mixed Marshalling body: | diff --git a/tenets/codelingo/go/nil-only-functions/README.md b/tenets/codelingo/go/nil-only-functions/README.md index 3b60f4b6..a501ec76 100644 --- a/tenets/codelingo/go/nil-only-functions/README.md +++ b/tenets/codelingo/go/nil-only-functions/README.md @@ -3,4 +3,4 @@ _by codelingo, part of their go bundle_ -[Load up in the codelingo.io/playground](https://codelingo.io/playground/?repo=github.com/codelingo/hub&dir=tenets/codelingo/go/nil-only-functions&tenet=codelingo/go/nil_only_functions) \ No newline at end of file +[Load up in the codelingo.io/playground](https://codelingo.io/playground/?repo=github.com/codelingo/hub&dir=tenets/codelingo/go/nil-only-functions&tenet=codelingo/go/nil-only-functions) diff --git a/tenets/codelingo/go/nil-only-functions/codelingo.yaml b/tenets/codelingo/go/nil-only-functions/codelingo.yaml index ec6f89cd..8fde3481 100644 --- a/tenets/codelingo/go/nil-only-functions/codelingo.yaml +++ b/tenets/codelingo/go/nil-only-functions/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: nil-only-functions - flows: + actions: codelingo/rewrite: codelingo/docs: title: Nil Only Functions diff --git a/tenets/codelingo/go/non-directional-chan/codelingo.yaml b/tenets/codelingo/go/non-directional-chan/codelingo.yaml new file mode 100644 index 00000000..55bc89cd --- /dev/null +++ b/tenets/codelingo/go/non-directional-chan/codelingo.yaml @@ -0,0 +1,14 @@ +tenets: + - name: non-directional-chan + actions: + codelingo/review: + comment: Returned channels or channel arguments should generally have a direction. + query: | + import codelingo/ast/go + + go.func_type(depth = any): + go.field_list: + go.field: + @review comment + go.chan_type: + dir == 3 diff --git a/tenets/codelingo/go/non-directional-chan/example.go b/tenets/codelingo/go/non-directional-chan/example.go new file mode 100644 index 00000000..ea674271 --- /dev/null +++ b/tenets/codelingo/go/non-directional-chan/example.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello, playground") +} + +func f(c chan int) {} // ISSUE + +func g(c <-chan int) {} + +func h(c chan<- int) {} + +func i() chan int { // ISSUE + return nil +} + +func j() <-chan int { + return nil +} + +func k() chan<- int { + return nil +} diff --git a/tenets/codelingo/go/non-directional-chan/expected.json b/tenets/codelingo/go/non-directional-chan/expected.json new file mode 100755 index 00000000..9772387a --- /dev/null +++ b/tenets/codelingo/go/non-directional-chan/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Returned channels or channel arguments should generally have a direction.", + "Filename": "example.go", + "Line": 11, + "Snippet": "}\n\nfunc f(c chan int) {} // ISSUE\n\nfunc g(c \u003c-chan int) {}" + }, + { + "Comment": "Returned channels or channel arguments should generally have a direction.", + "Filename": "example.go", + "Line": 17, + "Snippet": "func h(c chan\u003c- int) {}\n\nfunc i() chan int { // ISSUE\n\treturn nil\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/non-exiting-goroutine/codelingo.yaml b/tenets/codelingo/go/non-exiting-goroutine/codelingo.yaml new file mode 100644 index 00000000..1a800a59 --- /dev/null +++ b/tenets/codelingo/go/non-exiting-goroutine/codelingo.yaml @@ -0,0 +1,92 @@ +tenets: + - name: non-exiting-goroutine + actions: + codelingo/review: + comment: This goroutine does not exit and may cause a resource leak. + query: | + import codelingo/ast/go + + go.go_stmt(depth = any): + go.call_expr: + any_of: + # Match all non-labeled for loops + # TODO: investigate whether include depth issue preventing us from finding nested non-labeled for loops + # https://github.com/codelingo/codelingo/issues/455 + exclude: + go.labeled_stmt(depth = any): + include: + @review comment + go.for_stmt(depth = any): + # Assert that we don't return from the for loop + exclude: + go.element: + exclude: + # Returns found inside func literals will not break the for loop + go.func_lit(depth = any): + include: + go.return_stmt(depth = any) + # Assert that we don't break using a break statement where the for loop is the + # innermost "for", "switch", or "select" statement within the same function. + # https://golang.org/ref/spec#Break_statements + exclude: + go.element: + exclude: + any_of: + go.for_stmt(depth = any): + include: + go.branch_stmt(depth = any): + tok == "break" + go.select_stmt(depth = any): + include: + go.branch_stmt(depth = any): + tok == "break" + go.switch_stmt(depth = any): + include: + go.branch_stmt(depth = any): + tok == "break" + exclude: + go.ident + + # Match all labeled for loops + # TODO: use fragments to reduce code repetition between labeled and non-labeled sections + go.labeled_stmt(depth = any): + go.ident: + name as label + @review comment + go.for_stmt: + # Assert that we don't return from the for loop + exclude: + go.element: + exclude: + # Returns found inside func literals will not break the for loop + go.func_lit(depth = any): + include: + go.return_stmt(depth = any) + # Assert that we don't break to the for loop's label + exclude: + go.branch_stmt(depth = any): + tok == "break" + go.ident: + name == label + + # Assert that we don't break using a break statement where the for loop is the + # innermost "for", "switch", or "select" statement within the same function. + # https://golang.org/ref/spec#Break_statements + exclude: + go.element: + exclude: + any_of: + go.for_stmt(depth = any): + include: + go.branch_stmt(depth = any): + tok == "break" + go.select_stmt(depth = any): + include: + go.branch_stmt(depth = any): + tok == "break" + go.switch_stmt(depth = any): + include: + go.branch_stmt(depth = any): + tok == "break" + exclude: + go.ident diff --git a/tenets/codelingo/go/non-exiting-goroutine/example.go b/tenets/codelingo/go/non-exiting-goroutine/example.go new file mode 100644 index 00000000..c50f7e7b --- /dev/null +++ b/tenets/codelingo/go/non-exiting-goroutine/example.go @@ -0,0 +1,126 @@ +package main + +func main() { + go func() { + for { // ISSUE - labeled simple infinite loop + } + }() + + go func() { + for { + return + } + }() + + go func() { + for { // ISSUE - labeled loop with an out of scope return child + func() { + return + }() + } + }() + + go func() { + for { + break + } + }() + + go func() { // ISSUE - non-labeled simple infinite loop + for { + } + }() + + go func() { + for { + return + } + }() + + go func() { // ISSUE - non-labeled loop with an out of scope return child + for { + func() { + return + }() + } + }() + + go func() { + for { + break + } + }() + + go func() { // ISSUE - labeled loop with inapplicable break statement + l: + for { + switch 1 { + case 1: + break + } + continue l + } + }() + + go func() { + l: + for { + switch 1 { + case 1: + break l + } + } + }() + + go func() { // ISSUE - labeled loop with inapplicable break statement + l: + for { + select { + case <-make(chan int): + break + } + continue l + } + }() + + go func() { + l: + for { + select { + case <-make(chan int): + break l + } + } + }() + + go func() { // ISSUE - labeled loop with inapplicable break statement + l: + for { + for { + break + } + continue l + } + }() + + go func() { // ISSUE - labeled loop with inapplicable break statement + l: + for { + m: + for { + break m + } + continue l + } + }() + + go func() { + l: + for { + for { + break l + } + } + }() + +} diff --git a/tenets/codelingo/go/non-exiting-goroutine/expected.json b/tenets/codelingo/go/non-exiting-goroutine/expected.json new file mode 100644 index 00000000..80e87d93 --- /dev/null +++ b/tenets/codelingo/go/non-exiting-goroutine/expected.json @@ -0,0 +1,50 @@ +[ + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 41, + "Snippet": "\n\tgo func() { // ISSUE - non-labeled loop with an out of scope return child\n\t\tfor {\n\t\t\tfunc() {\n\t\t\t\treturn\n\t\t\t}()\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 5, + "Snippet": "func main() {\n\tgo func() {\n\t\tfor { // ISSUE - labeled simple infinite loop\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 30, + "Snippet": "\n\tgo func() { // ISSUE - non-labeled simple infinite loop\n\t\tfor {\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 16, + "Snippet": "\n\tgo func() {\n\t\tfor { // ISSUE - labeled loop with an out of scope return child\n\t\t\tfunc() {\n\t\t\t\treturn\n\t\t\t}()\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 56, + "Snippet": "\tgo func() { // ISSUE - labeled loop with inapplicable break statement\n\tl:\n\t\tfor {\n\t\t\tswitch 1 {\n\t\t\tcase 1:\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue l\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 77, + "Snippet": "\tgo func() { // ISSUE - labeled loop with inapplicable break statement\n\tl:\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase \u003c-make(chan int):\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue l\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 98, + "Snippet": "\tgo func() { // ISSUE - labeled loop with inapplicable break statement\n\tl:\n\t\tfor {\n\t\t\tfor {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue l\n\t\t}\n\t}()\n" + }, + { + "Comment": "This goroutine does not exit and may cause a resource leak.", + "Filename": "example.go", + "Line": 108, + "Snippet": "\tgo func() { // ISSUE - labeled loop with inapplicable break statement\n\tl:\n\t\tfor {\n\t\tm:\n\t\t\tfor {\n\t\t\t\tbreak m\n\t\t\t}\n\t\t\tcontinue l\n\t\t}\n\t}()\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/non-exiting-goroutine/issues.yaml b/tenets/codelingo/go/non-exiting-goroutine/issues.yaml new file mode 100644 index 00000000..57ee277f --- /dev/null +++ b/tenets/codelingo/go/non-exiting-goroutine/issues.yaml @@ -0,0 +1,2 @@ +issues: + - https://github.com/codelingo/codelingo/issues/457 \ No newline at end of file diff --git a/tenets/codelingo/go/pointer-leaves-method/codelingo.yaml b/tenets/codelingo/go/pointer-leaves-method/codelingo.yaml new file mode 100644 index 00000000..c78e5828 --- /dev/null +++ b/tenets/codelingo/go/pointer-leaves-method/codelingo.yaml @@ -0,0 +1,32 @@ +tenets: + - name: pointer-leaves-method + actions: + codelingo/review: + comment: The receiver pointer is returned from this method. Make a copy instead. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.field_list: + go.field: + go.names: + go.ident: + name as r + go.star_expr + go.block_stmt(depth = any): + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name as n + go.rhs: + @review comment + go.key_value_expr(depth = any): + exclude: + go.selector_expr(depth = any): + include: + go.ident(depth = any): + name == r + go.return_stmt(depth = any): + go.results: + go.ident: + name == n \ No newline at end of file diff --git a/tenets/codelingo/go/pointer-leaves-method/expected.json b/tenets/codelingo/go/pointer-leaves-method/expected.json new file mode 100644 index 00000000..048a9027 --- /dev/null +++ b/tenets/codelingo/go/pointer-leaves-method/expected.json @@ -0,0 +1,15 @@ +[ + { + "Comment": "The receiver pointer is returned from this method. Make a copy instead.", + "Filename": "main.go", + "Line": 29, + "Snippet": "\t\tid: 1,\n\t\tname: *b.name,\n\t\tbar: b, // ISSUE\n\t}\n\treturn f" + }, + { + "Comment": "The receiver pointer is returned from this method. Make a copy instead.", + "Filename": "race/race.go", + "Line": 65, + "Snippet": "func (n *NumHolder) NewUnsafeFirster() Firster {\n\tf := \u0026NumFirster{\n\t\tholder: n, // Issue\n\t}\n\treturn f" + } + ] + diff --git a/tenets/codelingo/go/pointer-leaves-method/main.go b/tenets/codelingo/go/pointer-leaves-method/main.go new file mode 100644 index 00000000..168483fe --- /dev/null +++ b/tenets/codelingo/go/pointer-leaves-method/main.go @@ -0,0 +1,32 @@ +package main + +import "pointer-leaves-method/race" + +type Bar struct { + name *string +} + +type Foo struct { + id int + name string + bar *Bar +} + +func main() { + race.Race() +} + +func NewBar(name string) *Bar { + return &Bar{ + name: &name, + } +} + +func (b *Bar) NewFooFromBar() *Foo { + f := &Foo{ + id: 1, + name: *b.name, + bar: b, // ISSUE + } + return f +} diff --git a/tenets/codelingo/go/pointer-leaves-method/race/race.go b/tenets/codelingo/go/pointer-leaves-method/race/race.go new file mode 100644 index 00000000..26c5d54e --- /dev/null +++ b/tenets/codelingo/go/pointer-leaves-method/race/race.go @@ -0,0 +1,89 @@ +package race + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// Firster interface +type Firster interface { + // First returns the first element in the underlying data structure + First() interface{} + // First sets the first element in the underlying data structure + SetFirst(interface{}) +} + +// NumHolder has an ID and holds a slice of ints +type NumHolder struct { + id int + nums []int +} + +// NumFirster contains a reference to a NumHolder and implements Firster +type NumFirster struct { + holder *NumHolder +} + +// Race attempts to data race on an implementation of Firster +func Race() error { + var wg sync.WaitGroup + h := NumHolder{ + id: 1, + nums: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + } + for i := 0; i < 10; i++ { + f := h.NewUnsafeFirster() + nf, ok := f.(*NumFirster) + if !ok { + return errors.New("failed to cast to NumFirster") + } + wg.Add(1) + go func(num int, nf *NumFirster) { + defer wg.Done() + nf.SetFirst(num) + // Do something... + time.Sleep(10 * time.Millisecond) + if first, ok := nf.First().(int); ok { + fmt.Printf("%p: %d\n", nf.holder, first) + } + }(i, nf) + } + wg.Wait() + return nil +} + +// NewUnsafeFirster creates a race-unsafe NumFirster +func (n *NumHolder) NewUnsafeFirster() Firster { + f := &NumFirster{ + holder: n, // Issue + } + return f +} + +// NewSafeFirster creates a race-safe NumFirster +func (n *NumHolder) NewSafeFirster() Firster { + numCopy := append([]int{}, n.nums...) + nCopy := &NumHolder{ + id: n.id, + nums: numCopy, + } + + f := &NumFirster{ + holder: nCopy, + } + return f +} + +// First implements Firster +func (n *NumFirster) First() interface{} { + return n.holder.nums[0] +} + +// SetFirst implements Firster +func (n *NumFirster) SetFirst(num interface{}) { + if newFirstVal, ok := num.(int); ok { + n.holder.nums[0] = newFirstVal + } +} diff --git a/tenets/codelingo/go/println-format-strings/codelingo.yaml b/tenets/codelingo/go/println-format-strings/codelingo.yaml index ef5f43d7..eb134c1f 100644 --- a/tenets/codelingo/go/println-format-strings/codelingo.yaml +++ b/tenets/codelingo/go/println-format-strings/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: println-format-strings - flows: + actions: codelingo/rewrite: codelingo/docs: title: Println Format Strings diff --git a/tenets/codelingo/go/reallocated-slice/codelingo.yaml b/tenets/codelingo/go/reallocated-slice/codelingo.yaml index 7e9788c3..51d6ce60 100644 --- a/tenets/codelingo/go/reallocated-slice/codelingo.yaml +++ b/tenets/codelingo/go/reallocated-slice/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: reallocated-slice - flows: + actions: codelingo/docs: title: Reallocated Slice body: Find slices that may be subject to reallocation pointer changes. Designed to catch problems like this https://github.com/juju/juju/commit/8ff9d72ebc07c0f1d2f048e5d0486335e637b313 diff --git a/tenets/codelingo/go/reassigned-error/codelingo.yaml b/tenets/codelingo/go/reassigned-error/codelingo.yaml new file mode 100644 index 00000000..63abc0b8 --- /dev/null +++ b/tenets/codelingo/go/reassigned-error/codelingo.yaml @@ -0,0 +1,49 @@ +funcs: +- name: compareFunc + type: asserter + body: | + function (a, b) { + return a < b; + } +- name: threeComparison + type: asserter + body: | + function (a, b, c) { + return (a < b) && (a > c); + } +tenets: +- name: reassigned-error + actions: + codelingo/review: + comment: Error was not checked before reassignment + query: | + import codelingo/ast/go + go.block_stmt(depth = any): + go.list: + @review comment + go.assign_stmt: + sibling_order as so1 + go.lhs: + go.ident: + name == "err" + go.assign_stmt: + sibling_order as so2 + compareFunc(so1, so2) + go.lhs: + go.ident: + name == "err" + exclude: + go.if_stmt: + sibling_order as so3 + threeComparison(so3, so2, so1) + go.binary_expr(depth = any): + go.ident: + name == "err" + exclude: + go.expr_stmt: + sibling_order as so4 + threeComparison(so4, so2, so1) + go.call_expr: + go.args: + go.ident: + name == "err" diff --git a/tenets/codelingo/go/reassigned-error/example.go b/tenets/codelingo/go/reassigned-error/example.go new file mode 100644 index 00000000..b9e1cc20 --- /dev/null +++ b/tenets/codelingo/go/reassigned-error/example.go @@ -0,0 +1,12 @@ +package main +import ( + "os" +) +func main() { + file, err := os.Open("test.txt") + file, err = os.Open("test.txt") + if err != nil { + panic(err) + } + defer file.Close() +} \ No newline at end of file diff --git a/tenets/codelingo/go/reassigned-error/expected.json b/tenets/codelingo/go/reassigned-error/expected.json new file mode 100644 index 00000000..5beabf7f --- /dev/null +++ b/tenets/codelingo/go/reassigned-error/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Error was not checked before reassignment", + "Filename": "example.go", + "Line": 6, + "Snippet": "\nfunc main() {\n\tfile, err := os.Open("test.txt"),\n\tfile, err = os.Open("test.txt")" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/remove-break-statement/codelingo.yaml b/tenets/codelingo/go/remove-break-statement/codelingo.yaml new file mode 100644 index 00000000..b17f0d3d --- /dev/null +++ b/tenets/codelingo/go/remove-break-statement/codelingo.yaml @@ -0,0 +1,22 @@ +tenets: + - name: remove-break-statement + actions: + codelingo/docs: + title: Remove Break Statement + body: The break statement that is needed at the end of each case is provided automatically in Go + codelingo/review: + comment: The break statement that is needed at the end of each case is provided automatically in Go + codelingo/rewrite: + query: | + import codelingo/ast/go + + + go.switch_stmt(depth = any): + go.case_clause(depth = any): + @rewrite --line "" + @review comment + go.branch_stmt(depth = any): + tok == "break" + # TODO: + # Exclude break statements that break to outer labels + # Only remove breaks at the end of the clause diff --git a/tenets/codelingo/go/remove-break-statement/expected.json b/tenets/codelingo/go/remove-break-statement/expected.json new file mode 100644 index 00000000..cc07a8e2 --- /dev/null +++ b/tenets/codelingo/go/remove-break-statement/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "The break statement that is needed at the end of each case is provided automatically in Go", + "Filename": "test.go", + "Line": 13, + "Snippet": "\tcase \"darwin\":\n\t\tfmt.Println(\"OS X.\")\n\t\tbreak\n\tcase \"linux\":\n\t\tfmt.Println(\"Linux.\")" + }, + { + "Comment": "The break statement that is needed at the end of each case is provided automatically in Go", + "Filename": "test.go", + "Line": 19, + "Snippet": "\tdefault:\n\t\tfmt.Printf(\"%s.\\n\", os)\n\t\tbreak\n\t}\n}" + }, + { + "Comment": "The break statement that is needed at the end of each case is provided automatically in Go", + "Filename": "test.go", + "Line": 16, + "Snippet": "\tcase \"linux\":\n\t\tfmt.Println(\"Linux.\")\n\t\tbreak\n\tdefault:\n\t\tfmt.Printf(\"%s.\\n\", os)" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/remove-break-statement/test.go b/tenets/codelingo/go/remove-break-statement/test.go new file mode 100644 index 00000000..dfdfa94c --- /dev/null +++ b/tenets/codelingo/go/remove-break-statement/test.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "runtime" +) + +func main() { + fmt.Print("Go runs on ") + switch os := runtime.GOOS; os { + case "darwin": + fmt.Println("OS X.") + break + case "linux": + fmt.Println("Linux.") + break + default: + fmt.Printf("%s.\n", os) + break + } +} diff --git a/tenets/codelingo/go/remove-empty-suite-functions/codelingo.yaml b/tenets/codelingo/go/remove-empty-suite-functions/codelingo.yaml new file mode 100644 index 00000000..71d8e291 --- /dev/null +++ b/tenets/codelingo/go/remove-empty-suite-functions/codelingo.yaml @@ -0,0 +1,28 @@ +tenets: + - name: remove-empty-suite-functions + flows: + codelingo/review: + comment: | + We're not obliged to implement suite functions like SetupTest, so there's + no reason to leave stubs of them lying around. + codelingo/rewrite: + place: holder + query: | + import codelingo/ast/go + + go.file(depth = any): + filename as fname + regex(/.*_test.go/, fname) + @review comment + @rewrite --replace "" + go.func_decl(depth = any): + go.field_list: + go.ident(depth = any): + name as receiver + # Assumes that all suites have "Suite" in their name. + # TODO: enforce this in another tenet or look at the type info on the + # receiver's definition + regex(/Suite/, receiver) + go.block_stmt: + exclude: + go.element \ No newline at end of file diff --git a/tenets/codelingo/go/remove-empty-suite-functions/example_test.go b/tenets/codelingo/go/remove-empty-suite-functions/example_test.go new file mode 100644 index 00000000..6971dd62 --- /dev/null +++ b/tenets/codelingo/go/remove-empty-suite-functions/example_test.go @@ -0,0 +1,29 @@ +package query_test + +import ( + "testing" + + jc "github.com/juju/testing/checkers" + . "gopkg.in/check.v1" +) + +type lSomeSuite struct { +} + +var _ = Suite(&lSomeSuite{ + doIngest: false, + buildStore: false, +}) + +func Test(t *testing.T) { + TestingT(t) +} + +func (s *SomeSuite) SetUpSuite(c *C) { + var err error + c.Assert(err, jc.ErrorIsNil) +} + +func (s *SomeSuite) SetUpTest(c *C) {} // Issue + +func (s *SomeSuite) TearDownTest(c *C) {} // Issue diff --git a/tenets/codelingo/go/remove-empty-suite-functions/expected.json b/tenets/codelingo/go/remove-empty-suite-functions/expected.json new file mode 100755 index 00000000..14f6c603 --- /dev/null +++ b/tenets/codelingo/go/remove-empty-suite-functions/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "We're not obliged to implement suite functions like SetupTest, so there's\nno reason to leave stubs of them lying around.\n", + "Filename": "example_test.go", + "Line": 39, + "Snippet": "}\n\nfunc (s *SomeSuite) SetUpTest(c *C) {} // Issue\n\nfunc (s *SomeSuite) TearDownTest(c *C) {} // Issue" + }, + { + "Comment": "We're not obliged to implement suite functions like SetupTest, so there's\nno reason to leave stubs of them lying around.\n", + "Filename": "example_test.go", + "Line": 41, + "Snippet": "func (s *SomeSuite) SetUpTest(c *C) {} // Issue\n\nfunc (s *SomeSuite) TearDownTest(c *C) {} // Issue\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/shadowed-variable/codelingo.yaml b/tenets/codelingo/go/shadowed-variable/codelingo.yaml new file mode 100644 index 00000000..e45c9d1e --- /dev/null +++ b/tenets/codelingo/go/shadowed-variable/codelingo.yaml @@ -0,0 +1,27 @@ +tenets: + - name: shadowed-variable + actions: + codelingo/review: + comment: Variable {{x}} shadows a variable of the same name declared in an outer scope. + query: | + import codelingo/ast/go + + go.file(depth = any): + go.decls: + go.func_decl: + go.block_stmt: + go.list: + go.assign_stmt: + go.lhs: + go.ident: + name as x + go.block_stmt(depth = any): + go.list: + go.assign_stmt: + tok == ":=" + go.lhs: + @review comment + go.ident: + name == x + name != "err" + name != "_" diff --git a/tenets/codelingo/go/shadowed-variable/examples.go b/tenets/codelingo/go/shadowed-variable/examples.go new file mode 100644 index 00000000..5e3df2d8 --- /dev/null +++ b/tenets/codelingo/go/shadowed-variable/examples.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + foo() + bar() + baz() +} + +func baz() { + file, err := os.Open("test.txt") + if err != nil { + return + } + defer file.Close() + + fileList := []string{"test1.txt"} + for _, f:= range fileList { + fileN, err := os.Open(f) // Acceptable + if err != nil { + return + } + defer fileN.Close() + } +} + +func foo() { + x := 1 // Acceptable + fmt.Println(x) + { + x = 5 // Acceptable + x := 2 // Issue + fmt.Println(x) + } + x = x + 2 + fmt.Println(x) +} + +func bar() { //nested test + x := "test" + { + if true { + x := "newVariable" // Issue + fmt.Println(x) + } + } + fmt.Println(x) +} \ No newline at end of file diff --git a/tenets/codelingo/go/shadowed-variable/expected.json b/tenets/codelingo/go/shadowed-variable/expected.json new file mode 100644 index 00000000..22c0a4a8 --- /dev/null +++ b/tenets/codelingo/go/shadowed-variable/expected.json @@ -0,0 +1,15 @@ +[ + { + "Comment": "Variable x shadows a variable of the same name declared in an outer scope.", + "Filename": "test.go", + "Line": 28, + "Snippet": "\t{\n\t\tif x == \"test\" {\n\t\t\tx := \"newVariable\" // Issue\n\t\t\tfmt.Println(x)\n\t\t}" + }, + { + "Comment": "Variable y shadows a variable of the same name declared in an outer scope.", + "Filename": "test.go", + "Line": 17, + "Snippet": "\t{\n\t\ty = 5 // Acceptable\n\t\ty := 2 // Issue\n\t\tfmt.Println(y)\n\t}" + } +] + \ No newline at end of file diff --git a/tenets/codelingo/go/shared-count-datarace/codelingo.yaml b/tenets/codelingo/go/shared-count-datarace/codelingo.yaml new file mode 100644 index 00000000..1a3e9846 --- /dev/null +++ b/tenets/codelingo/go/shared-count-datarace/codelingo.yaml @@ -0,0 +1,62 @@ +tenets: + - name: shared-count-datarace + actions: + codelingo/review: + comment: Goroutines created in for loops that use the iterator of the loop internally should instead use a copy made at the time of the goroutines creation by passing it as an argument. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name as wgName + go.rhs: + go.ident(depth = any): + name == "WaitGroup" + any_of: + @review comment + go.for_stmt(depth = any): + go.binary_expr: + go.ident: + name as countName + go.inc_dec_stmt: + go.ident: + name == countName + exclude: + go.assign_stmt(depth = any): + go.lhs: + go.ident: + name == "i" + go.rhs: + go.ident: + name == "i" + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == wgName + go.ident: + name == "Add" + go.go_stmt(depth = any): + go.call_expr: + go.func_lit: + exclude: + go.func_type: + go.field_list: + go.field + go.ident(depth = any): + name == countName + @review comment + go.range_stmt(depth = any): + go.ident: + name as countName + go.go_stmt(depth = any): + go.call_expr: + go.func_lit: + exclude: + go.func_type: + go.field_list: + go.field + go.ident(depth = any): + name == countName + diff --git a/tenets/codelingo/go/shared-count-datarace/example.go b/tenets/codelingo/go/shared-count-datarace/example.go new file mode 100644 index 00000000..3a35f027 --- /dev/null +++ b/tenets/codelingo/go/shared-count-datarace/example.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + "sync" +) + +func main() { + + // Issue - When a loop creates goroutines which needs to use the iterator of the + // loop the iterator should always be provided as an argument to the function. + // If this is not done, as in the below case, then we risk having an unexpected + // value of the iterator as the parent goroutine may have incremented its value + // already. + for i := 0; i < 5; i++ { + go func() { + fmt.Println(i) + }() + } + + // Non Issue + for i := 0; i < 5; i++ { + go func(i int) { + fmt.Println(i) + }(i) + } + + slice := []int{0,1,2,3} + + // Issue + for _, i := range slice { + go func() { + fmt.Println(i) + }() + } + + // Issue + for j, i := range slice { + go func() { + fmt.Println(j) + }() + } + + // Non Issue + for _, i := range slice { + go func(i int) { + fmt.Println(i) + }(i) + } + + // Non Issue + for j, i := range slice { + go func(j int) { + fmt.Println(j) + }(j) + } + + size := 4 + + // Non Issue, the goroutines in this loop use the upper bound of the loop which doesn't change therefore there is no issue + for i := 0; i < size; i++ { + go func() { + fmt.Println(size) + }() + } + + // Non Issue, the use of a WaitGroup here means there is no risk of a race condition + wg := sync.WaitGroup{} + for i := 0; i < 5; i++ { + i := i + wg.Add(1) + go func() { + defer wg.Done() + fmt.Prinltn(i) + }() + } + wg.Wait() +} diff --git a/tenets/codelingo/go/shared-count-datarace/expected.json b/tenets/codelingo/go/shared-count-datarace/expected.json new file mode 100755 index 00000000..139ac05a --- /dev/null +++ b/tenets/codelingo/go/shared-count-datarace/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "Goroutines created in for loops that use the iterator of the loop internally should instead use a copy made at the time of the goroutines creation by passing it as an argument.", + "Filename": "example.go", + "Line": 12, + "Snippet": "\t// value of i. However this may not always be the case so the goroutine should\n\t// be given a copy of I as an argument as in the second for loop.\n\tfor i := 0; i \u003c 5; i++ {\n\t\tgo func() {\n\t\t\tfmt.Println(i)\n\t\t}()\n\t}\n\n\t// Non Issue" + }, + { + "Comment": "Goroutines created in for loops that use the iterator of the loop internally should instead use a copy made at the time of the goroutines creation by passing it as an argument.", + "Filename": "example.go", + "Line": 35, + "Snippet": "\n\t// Issue\n\tfor j, i := range slice {\n\t\tgo func() {\n\t\t\tfmt.Println(j)\n\t\t}()\n\t}\n\n\tfor _, i := range slice {" + }, + { + "Comment": "Goroutines created in for loops that use the iterator of the loop internally should instead use a copy made at the time of the goroutines creation by passing it as an argument.", + "Filename": "example.go", + "Line": 28, + "Snippet": "\n\t// Issue\n\tfor _, i := range slice {\n\t\tgo func() {\n\t\t\tfmt.Println(i)\n\t\t}()\n\t}\n\n\t// Issue" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/sprintf/codelingo.yaml b/tenets/codelingo/go/sprintf/codelingo.yaml index 522e0dae..ae6b3e36 100644 --- a/tenets/codelingo/go/sprintf/codelingo.yaml +++ b/tenets/codelingo/go/sprintf/codelingo.yaml @@ -1,6 +1,9 @@ tenets: - name: sprintf-error - flows: + vars: + newline: | + errors.Errorf("{{formatString}}") + actions: codelingo/rewrite: codelingo/docs: title: Sprintf Error @@ -12,15 +15,14 @@ tenets: import codelingo/ast/go @review comment + @rewrite -r "{{newline}}" go.call_expr(depth = any): go.selector_expr: go.ident: name == "errors" - @rewrite -r "Errorf" go.ident: name == "New" go.args: - @rewrite -r "\"{{formatString}}\"" go.call_expr: go.selector_expr: go.ident: diff --git a/tenets/codelingo/go/sprintf/expected.json b/tenets/codelingo/go/sprintf/expected.json new file mode 100644 index 00000000..13ddb6dd --- /dev/null +++ b/tenets/codelingo/go/sprintf/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Should replace errors.New(fmt.Sprintf(...)) with errors.Errorf(...).", + "Filename": "example.go", + "Line": 16, + "Snippet": "func doStuffA(param string) error {\n\t// bad\n\treturn errors.New(fmt.Sprintf(\"Don't call with \\\"%s\\\" param - it will literally do nothing!\", param))\n}\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/surprising-for-direction/codelingo.yaml b/tenets/codelingo/go/surprising-for-direction/codelingo.yaml new file mode 100644 index 00000000..4eb159d3 --- /dev/null +++ b/tenets/codelingo/go/surprising-for-direction/codelingo.yaml @@ -0,0 +1,39 @@ +tenets: +- name: surprising-for-direction + actions: + codelingo/docs: + title: Surprising for direction + body: | + A loop which counts upwards should check an upper bound; a loop which counts downwards + should check a lower bound. Violating this rule is not necessarily incorrect, but it's + invariably surprising and likely to confuse readers. + codelingo/review: + comment: | + The loop's exit condition is not consistent with its step operation. + query: | + import codelingo/ast/gotypes + + gotypes.for_stmt(depth=any): + @review comment + gotypes.cond: + gotypes.binary_expr: + op as op + gotypes.post: + gotypes.element: + # matches both assign_stmt and inc_dec_stmt + token as token + + mismatch(op, token) + +funcs: +- name: mismatch + type: asserter + body: | + function(op, token) { + if (op === "<" || op === "<=") { + return (token == "--" || token == "-=") + } + if (op === ">" || op === ">=") { + return (token == "++" || token == "+=") + } + } diff --git a/tenets/codelingo/go/surprising-for-direction/expected.json b/tenets/codelingo/go/surprising-for-direction/expected.json new file mode 100644 index 00000000..03978616 --- /dev/null +++ b/tenets/codelingo/go/surprising-for-direction/expected.json @@ -0,0 +1,20 @@ +[ + { + "Comment": "The loop's exit condition is not consistent with its step operation.\n", + "Filename": "sample.go", + "Line": 15, + "Snippet": "\nfunc bad() {\n\tfor i := 0; i > 10; i++ {\n\t\tfmt.Println(i)\n\t}" + }, + { + "Comment": "The loop's exit condition is not consistent with its step operation.\n", + "Filename": "sample.go", + "Line": 18, + "Snippet": "\t\tfmt.Println(i)\n\t}\n\tfor i := 0; i <= 10; i-- {\n\t\tfmt.Println(i)\n\t}" + }, + { + "Comment": "The loop's exit condition is not consistent with its step operation.\n", + "Filename": "sample.go", + "Line": 21, + "Snippet": "\t\tfmt.Println(i)\n\t}\n\tfor i := uint(10); i <= 10; i-- {\n\t\t// This is... correct, but confusing.\n\t\tfmt.Println(i)" + } +] diff --git a/tenets/codelingo/go/surprising-for-direction/sample.go b/tenets/codelingo/go/surprising-for-direction/sample.go new file mode 100644 index 00000000..c739ff59 --- /dev/null +++ b/tenets/codelingo/go/surprising-for-direction/sample.go @@ -0,0 +1,25 @@ +package sample + +import "fmt" + +func good() { + for i := 0; i < 10; i++ { + fmt.Println(i) + } + for i := 10; i >= 0; i-- { + fmt.Println(i) + } +} + +func bad() { + for i := 0; i > 10; i++ { + fmt.Println(i) + } + for i := 0; i <= 10; i-- { + fmt.Println(i) + } + for i := uint(10); i <= 10; i-- { + // This is... correct, but confusing. + fmt.Println(i) + } +} diff --git a/tenets/codelingo/go/tested/codelingo.yaml b/tenets/codelingo/go/tested/codelingo.yaml index 59dba065..2693e3f2 100644 --- a/tenets/codelingo/go/tested/codelingo.yaml +++ b/tenets/codelingo/go/tested/codelingo.yaml @@ -17,9 +17,16 @@ funcs: function (str, substr) { return str.indexOf(substr) == -1 } + - name: isExported + type: asserter + body: | + function(name) { + // TODO: support unicode uppercase + return !!name.match(/^[A-Z]/) + } tenets: - name: find-funcs - flows: + actions: codelingo/docs: title: NewConcat body: Example tenet that finds all exported functions that do not have a corresponding test function. @@ -34,12 +41,12 @@ tenets: @review comment go.func_decl: go.ident: - public == "true" name as funcName + isExported(funcName) exclude: go.file(depth = any): filename == testFile(filename) go.decls: go.func_decl: go.ident: - name == newConcat("Test", funcName) \ No newline at end of file + name == newConcat("Test", funcName) diff --git a/tenets/codelingo/go/tested/expected.json b/tenets/codelingo/go/tested/expected.json index 4fdf8655..7ae659c6 100644 --- a/tenets/codelingo/go/tested/expected.json +++ b/tenets/codelingo/go/tested/expected.json @@ -1,45 +1,14 @@ [ { - "name": "find-funcs", - "position": { - "start": { - "filename": "main.go", - "Offset": 90, - "Line": 9, - "Column": 1 - }, - "end": { - "filename": "main.go", - "Offset": 111, - "Line": 9, - "Column": 22 - } - }, - "comment": "This exported function does not have a corresponding test function.", - "ctxBefore": "\n// SecondPrint also prints", - "lineText": "func SecondPrint() {}", - "newCode": true + "Comment": "This exported function does not have a corresponding test function.", + "Filename": "example.go", + "Line": 12, + "Snippet": "\n// No test function\nfunc (a *aStruct) SayA() string {\n\tif a.b == nil {\n\t\treturn \"end\"\n\t}\n\treturn \"I'm a \" // + a.b.SayB()\n}\n\ntype bStruct struct {" }, { - "name": "find-funcs", - "position": { - "start": { - "filename": "example.go", - "Offset": 94, - "Line": 12, - "Column": 1 - }, - "end": { - "filename": "example.go", - "Offset": 197, - "Line": 17, - "Column": 2 - } - }, - "comment": "This exported function does not have a corresponding test function.", - "ctxBefore": "\n// No test function", - "lineText": "func (a *aStruct) SayA() string {\n\tif a.b == nil {\n\t\treturn \"end\"\n\t}\n\treturn \"I'm a \" // + a.b.SayB()\n}", - "ctxAfter": "\ntype bStruct struct {", - "newCode": true + "Comment": "This exported function does not have a corresponding test function.", + "Filename": "main.go", + "Line": 9, + "Snippet": "\n// SecondPrint also prints\nfunc SecondPrint() {}\n" } - ] + ] \ No newline at end of file diff --git a/tenets/codelingo/go/ticker-in-for-select/README.md b/tenets/codelingo/go/ticker-in-for-select/README.md new file mode 100644 index 00000000..71b9dfbb --- /dev/null +++ b/tenets/codelingo/go/ticker-in-for-select/README.md @@ -0,0 +1,3 @@ +# ticker-in-for-select + +_by codelingo, part of their go bundle_ diff --git a/tenets/codelingo/go/ticker-in-for-select/codelingo.yaml b/tenets/codelingo/go/ticker-in-for-select/codelingo.yaml new file mode 100644 index 00000000..d727cd26 --- /dev/null +++ b/tenets/codelingo/go/ticker-in-for-select/codelingo.yaml @@ -0,0 +1,34 @@ +tenets: + - name: ticker-in-for-select + actions: + codelingo/review: + comment: Make one ticker as opposed to making a new timer each iteration. + query: | + import codelingo/ast/go + + any_of: + go.range_stmt(depth = any): + go.select_stmt(depth = any): + go.comm_clause(depth = any): + go.expr_stmt: + go.unary_expr: + @review comment + go.call_expr: + go.selector_expr: + go.ident: + name == "time" + go.ident: + name == "After" + #TODO: use path or macro facts for repeated code + go.for_stmt(depth = any): + go.select_stmt(depth = any): + go.comm_clause(depth = any): + go.expr_stmt: + go.unary_expr: + @review comment + go.call_expr: + go.selector_expr: + go.ident: + name == "time" + go.ident: + name == "After" diff --git a/tenets/codelingo/go/ticker-in-for-select/example.go b/tenets/codelingo/go/ticker-in-for-select/example.go new file mode 100644 index 00000000..28a43a1e --- /dev/null +++ b/tenets/codelingo/go/ticker-in-for-select/example.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" +) + +func main() { + for { + select { + case <-time.After(50 * time.Second): + fmt.Println("Hello, playground") + } + } + + for _, i := range nums { + select { + case <-time.After(50 * time.Second): + fmt.Println("Hello, playground") + } + } +} diff --git a/tenets/codelingo/go/ticker-in-for-select/expected.json b/tenets/codelingo/go/ticker-in-for-select/expected.json new file mode 100755 index 00000000..39fb6251 --- /dev/null +++ b/tenets/codelingo/go/ticker-in-for-select/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Make one ticker as opposed to making a new timer each iteration.", + "Filename": "example.go", + "Line": 17, + "Snippet": "\tfor _, i := range nums {\n\t\tselect {\n\t\tcase \u003c-time.After(50 * time.Second):\n\t\t\tfmt.Println(\"Hello, playground\")\n\t\t}" + }, + { + "Comment": "Make one ticker as opposed to making a new timer each iteration.", + "Filename": "example.go", + "Line": 10, + "Snippet": "\tfor {\n\t\tselect {\n\t\tcase \u003c-time.After(50 * time.Second):\n\t\t\tfmt.Println(\"Hello, playground\")\n\t\t}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/todo/codelingo.yaml b/tenets/codelingo/go/todo/codelingo.yaml index 25cf68c2..8045e2b1 100644 --- a/tenets/codelingo/go/todo/codelingo.yaml +++ b/tenets/codelingo/go/todo/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: todo-comments - flows: + actions: codelingo/docs: title: Todo Comments body: | diff --git a/tenets/codelingo/go/typed-nil-pointer/codelingo.yaml b/tenets/codelingo/go/typed-nil-pointer/codelingo.yaml new file mode 100644 index 00000000..0d8e8970 --- /dev/null +++ b/tenets/codelingo/go/typed-nil-pointer/codelingo.yaml @@ -0,0 +1,42 @@ +funcs: + - name: isPointer + type: asserter + body: | + function(varName) { + return varName.indexOf("*") !== -1 + } +tenets: + - name: find-typed-nil + actions: + codelingo/review: + comment: If typed pointer `{{returnName}}` is nil, return nil explicitly + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.func_type: + go.field_list: + sibling_order == 1 + go.field: + go.interface_type + @review comment + go.return_stmt(depth = any): + go.results: + go.ident(depth = any): + name as returnName + type as returnType + type != "interface{}" + isPointer(returnType) + exclude: + go.if_stmt(depth = any): + go.binary_expr: + go.ident: + name == returnName + go.ident: + name == "nil" + go.block_stmt: + go.list: + go.return_stmt: + go.results: + go.ident: + name == "nil" \ No newline at end of file diff --git a/tenets/codelingo/go/typed-nil-pointer/expected.json b/tenets/codelingo/go/typed-nil-pointer/expected.json new file mode 100644 index 00000000..60e59496 --- /dev/null +++ b/tenets/codelingo/go/typed-nil-pointer/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "If typed pointer `result` is nil, return nil explicitly", + "Filename": "test.go", + "Line": 18, + "Snippet": "\t}\n\n\treturn result //ISSUE\n}\n" + } +] \ No newline at end of file diff --git a/tenets/codelingo/go/typed-nil-pointer/test.go b/tenets/codelingo/go/typed-nil-pointer/test.go new file mode 100644 index 00000000..0592ae37 --- /dev/null +++ b/tenets/codelingo/go/typed-nil-pointer/test.go @@ -0,0 +1,54 @@ +package main + +import "fmt" + +type Person struct { + FirstName string + LastName string + Age int +} + +func test1(arg int) interface{} { + var result *Person = nil + + if arg > 0 { + result = &Person{} + } + + return result //ISSUE +} + +func test2(arg int) interface{} { + var result *Person = nil + + if arg > 0 { + result = &Person{} + } + + if result == nil { + return nil + } + + return result +} + +func main() { + fmt.Println("test1 func") + if res := test1(-1); res != nil { + fmt.Println("non nil result:", res) + fmt.Println(res == nil) + fmt.Printf("%T", res) + fmt.Println() + } else { + fmt.Println("nil result", res) + } + fmt.Println() + fmt.Println("test2 func") + if res := test2(-1); res != nil { + fmt.Println("non nil result:", res) + } else { + fmt.Println("nil result", res) + fmt.Println(res == nil) + fmt.Printf("%T", res) + } +} diff --git a/tenets/codelingo/go/unchecked-type-assertion/codelingo.yaml b/tenets/codelingo/go/unchecked-type-assertion/codelingo.yaml new file mode 100644 index 00000000..9ad29d03 --- /dev/null +++ b/tenets/codelingo/go/unchecked-type-assertion/codelingo.yaml @@ -0,0 +1,14 @@ +tenets: + - name: unchecked-type-assertion + actions: + codelingo/review: + comment: Unchecked type assertions can cause panics. Check for success with the x, ok := y.(type) style. + query: | + import codelingo/ast/go + + go.assign_stmt(depth = any): + go.lhs: + child_count == 1 + go.rhs: + @review comment + go.type_assert_expr \ No newline at end of file diff --git a/tenets/codelingo/go/unchecked-type-assertion/expected.json b/tenets/codelingo/go/unchecked-type-assertion/expected.json new file mode 100755 index 00000000..5d9abf7a --- /dev/null +++ b/tenets/codelingo/go/unchecked-type-assertion/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Unchecked type assertions can cause panics. Check for success with the x, ok := y.(type) style.", + "Filename": "main.go", + "Line": 17, + "Snippet": "\nfunc unchecked(in interface{}) {\n\ts := in.(string) // ISSUE\n\tfmt.Println(s)\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/unchecked-type-assertion/main.go b/tenets/codelingo/go/unchecked-type-assertion/main.go new file mode 100644 index 00000000..31afa9d2 --- /dev/null +++ b/tenets/codelingo/go/unchecked-type-assertion/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "errors" + "fmt" +) + +func main() { + s := 3 + if err := checked(s); err != nil { + fmt.Println(err.Error()) + } + unchecked(s) +} + +func unchecked(in interface{}) { + s := in.(string) // ISSUE + fmt.Println(s) +} + +func checked(in interface{}) error { + s, ok := in.(string) // No issue + if !ok { + return errors.New("failed to assert type") + } + + fmt.Println(s) + return nil +} diff --git a/tenets/codelingo/go/unconvert/codelingo.yaml b/tenets/codelingo/go/unconvert/codelingo.yaml index c97b9896..71042b4c 100644 --- a/tenets/codelingo/go/unconvert/codelingo.yaml +++ b/tenets/codelingo/go/unconvert/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: unconvert - flows: + actions: codelingo/rewrite: codelingo/docs: title: Unconvert diff --git a/tenets/codelingo/go/unnecessary-parentheses/codelingo.yaml b/tenets/codelingo/go/unnecessary-parentheses/codelingo.yaml new file mode 100644 index 00000000..67577e7e --- /dev/null +++ b/tenets/codelingo/go/unnecessary-parentheses/codelingo.yaml @@ -0,0 +1,32 @@ +funcs: + - name: removeParen + type: resolver + body: | + function(parenExpr) { + parenExpr = parenExpr.replace("(","") + parenExpr = parenExpr.replace(")","") + return parenExpr + } + + +tenets: + - name: unnecessary-parentheses + actions: + codelingo/docs: + title: Unnecessary Parentheses + body: | + Avoid sunnecessary parentheses to make your code easier to read. + codelingo/review: + comment: Avoid sunnecessary parentheses to make your code easier to read. + codelingo/rewrite: + query: | + import codelingo/ast/go + + @review comment + @rewrite --replace "{{removeParen(raw1)}}" + go.paren_expr(depth = any): + raw as raw1 + any_of: + go.basic_lit + go.ident + go.index_expr \ No newline at end of file diff --git a/tenets/codelingo/go/unnecessary-parentheses/expected.json b/tenets/codelingo/go/unnecessary-parentheses/expected.json new file mode 100644 index 00000000..58788081 --- /dev/null +++ b/tenets/codelingo/go/unnecessary-parentheses/expected.json @@ -0,0 +1,26 @@ +[ + { + "Comment": "Avoid sunnecessary parentheses to make your code easier to read.", + "Filename": "test.go", + "Line": 7, + "Snippet": "func main() {\n\tvar a [2]string\n\ta[0] = (\"Hello\")\n\tfmt.Println((a[0]))\n\tb := (1.2)" + }, + { + "Comment": "Avoid sunnecessary parentheses to make your code easier to read.", + "Filename": "test.go", + "Line": 9, + "Snippet": "\ta[0] = (\"Hello\")\n\tfmt.Println((a[0]))\n\tb := (1.2)\n\tfmt.Println((b))\n}" + }, + { + "Comment": "Avoid sunnecessary parentheses to make your code easier to read.", + "Filename": "test.go", + "Line": 10, + "Snippet": "\tfmt.Println((a[0]))\n\tb := (1.2)\n\tfmt.Println((b))\n}" + }, + { + "Comment": "Avoid sunnecessary parentheses to make your code easier to read.", + "Filename": "test.go", + "Line": 8, + "Snippet": "\tvar a [2]string\n\ta[0] = (\"Hello\")\n\tfmt.Println((a[0]))\n\tb := (1.2)\n\tfmt.Println((b))" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/go/unnecessary-parentheses/test.go b/tenets/codelingo/go/unnecessary-parentheses/test.go new file mode 100644 index 00000000..6be2922b --- /dev/null +++ b/tenets/codelingo/go/unnecessary-parentheses/test.go @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + var a [2]string + a[0] = ("Hello") + fmt.Println((a[0])) + b := (1.2) + fmt.Println((b)) +} diff --git a/tenets/codelingo/go/unsafe-go-routine-variables/codelingo.yaml b/tenets/codelingo/go/unsafe-go-routine-variables/codelingo.yaml index 1d3c74b4..85fd704d 100644 --- a/tenets/codelingo/go/unsafe-go-routine-variables/codelingo.yaml +++ b/tenets/codelingo/go/unsafe-go-routine-variables/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: unsafe-go-routine-variables - flows: + actions: codelingo/docs: title: Unsafe Go Routine Variables body: Example tenet that finds unsafe variables in goroutines. diff --git a/tenets/codelingo/go/unused-private-functions/codelingo.yaml b/tenets/codelingo/go/unused-private-functions/codelingo.yaml index 5923eb7a..1d6d00d4 100644 --- a/tenets/codelingo/go/unused-private-functions/codelingo.yaml +++ b/tenets/codelingo/go/unused-private-functions/codelingo.yaml @@ -1,6 +1,17 @@ +funcs: + - name: isCandidate + type: asserter + body: | + function(name) { + if (name == "main" || name == "init") { + return false + } + // TODO: support unicode uppercase + return !name.match(/^[A-Z]/) + } tenets: - name: find-unused-private-funcs - flows: + actions: codelingo/rewrite: codelingo/review: comment: This private function is not used in the package. @@ -11,11 +22,9 @@ tenets: go.func_decl(depth = any): @review comment go.ident: - private == "true" name as funcName - name != "main" - name != "init" + isCandidate(funcName) exclude: go.call_expr(depth = any): go.ident: - name as funcName \ No newline at end of file + name == funcName diff --git a/tenets/codelingo/go/writes-to-async-pointer/codelingo.yaml b/tenets/codelingo/go/writes-to-async-pointer/codelingo.yaml new file mode 100644 index 00000000..1773fc6c --- /dev/null +++ b/tenets/codelingo/go/writes-to-async-pointer/codelingo.yaml @@ -0,0 +1,58 @@ +tenets: + - name: writes-to-async-pointer + actions: + codelingo/review: + comment: Writing to a reference passed to an asynchronous function is dengerous when other reads and writes have been called from the same context. + query: | + import codelingo/ast/go + + @review comment + go.func_decl(depth = any): + go.go_stmt(depth = any): + any_of: + go.call_expr: + edge("calls"): + go.func_decl: + go.field(depth = any): + go.names: + go.ident: + name as pointer + go.star_expr + go.assign_stmt(depth = any): + go.lhs(depth = any): + go.ident(depth = any): + name == pointer + go.call_expr(depth = any): + go.func_lit: + go.field(depth = any): + go.names: + go.ident: + name as pointer + go.star_expr + go.assign_stmt(depth = any): + go.lhs(depth = any): + go.ident(depth = any): + name == pointer + go.go_stmt(depth = any): + any_of: + go.call_expr: + edge("calls"): + go.func_decl: + go.field(depth = any): + go.names: + go.ident: + name == pointer + go.star_expr + go.assign_stmt(depth = any): + go.ident(depth = any): + name == pointer + go.call_expr(depth = any): + go.func_lit: + go.field(depth = any): + go.names: + go.ident: + name == pointer + go.star_expr + go.assign_stmt(depth = any): + go.ident(depth = any): + name == pointer diff --git a/tenets/codelingo/go/writes-to-async-pointer/example.go b/tenets/codelingo/go/writes-to-async-pointer/example.go new file mode 100644 index 00000000..a7bf1477 --- /dev/null +++ b/tenets/codelingo/go/writes-to-async-pointer/example.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" +) + +type thing struct { + name string +} + +func readVulnerableField(t *thing) { + fmt.Println(t.name) +} + +func readSafeField(t thing) { + fmt.Println(t.name) +} + +func writeVulnerableField(t *thing) { + t.name = "car" +} + +func writeSafeField(t thing) { + t.name = "boat" +} + +// Concurrent read and writes to a field of a copy of an instance of thing. No Issue +func concurrentCopyReadWrites() { + + t := thing{"Object"} + + go readSafeField(t) + + go func(t thing) { + fmt.Println(t.name) + }(t) + + go func(t thing) { + t.name = "bus" + }(t) +} + +// Concurrent reads to a field of a copy of an instance of thing. No Issue +func concurrentCopyReads() { + + t := thing{"Object"} + + go readSafeField(t) + + go func(t thing) { + fmt.Println(t.name) + }(t) +} + +// Concurrent writes to a field of a copy of an instance of thing. No Issue +func concurrentCopyWrites() { + + t := thing{"Object"} + + go writeSafeField(t) + + go func(t thing) { + t.name = "plane" + }(t) +} + +// Concurrent reads to a field of a pointer to an instance of thing. No Issue +func concurrentPointerReads() { + + t := &thing{"Pointer"} + + go readVulnerableField(t) + + go func(t *thing) { + fmt.Println(t.name) + }(t) +} + +// Concurrent writes to a field of a pointer to an instance of thing. Issue +func concurrentPointerWrites() { + + t := &thing{"Pointer"} + + go writeVulnerableField(t) + + go func(t *thing) { + t.name = "bus" + }(t) +} + +// Concurrent reads and writes to a field of a pointer to an instance of thing. Issue +func concurrentPointerReadWrites() { + + t := &thing{"Pointer"} + + go readVulnerableField(t) + + go writeVulnerableField(t) + + go func(t *thing) { + fmt.Println(t.name) + }(t) + + go func(t *thing) { + t.name = "car" + }(t) + +} + +func main() { + concurrentCopyReads() + concurrentCopyWrites() + concurrentCopyReadWrites() + concurrentPointerReads() + concurrentPointerWrites() + concurrentPointerReadWrites() +} diff --git a/tenets/codelingo/go/writes-to-async-pointer/expected.json b/tenets/codelingo/go/writes-to-async-pointer/expected.json new file mode 100755 index 00000000..8e73505f --- /dev/null +++ b/tenets/codelingo/go/writes-to-async-pointer/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Writing to a reference passed to an asynchronous function is dengerous when other reads and writes have been called from the same context.", + "Filename": "example.go", + "Line": 110, + "Snippet": "\n// Concurrent reads and writes to a field of a pointer to an instance of thing. Issue\nfunc concurrentPointerReadWrites() {\n\n\tt := \u0026thing{\"Pointer\"}\n\n\treadVulnerableField(t)\n\n\tgo readVulnerableField(t)\n\n\tgo writeVulnerableField(t)\n\n\tgo func(t *thing) {\n\t\tfmt.Println(t.name)\n\t}(t)\n\n\tgo func(t *thing) {\n\t\tt.name = \"car\"\n\t}(t)\n\n\tt.readFromPointer()\n\n\tt.writeToPointer(\"plane\")\n\n\tgo t.writeToPointer(\"boat\")\n}\n\nfunc main() {" + }, + { + "Comment": "Writing to a reference passed to an asynchronous function is dengerous when other reads and writes have been called from the same context.", + "Filename": "example.go", + "Line": 96, + "Snippet": "\n// Concurrent writes to a field of a pointer to an instance of thing. Issue\nfunc concurrentPointerWrites() {\n\n\tt := \u0026thing{\"Pointer\"}\n\n\twriteVulnerableField(t)\n\n\tgo writeVulnerableField(t)\n\n\tgo func(t *thing) {\n\t\tt.name = \"bus\"\n\t}(t)\n}\n\n// Concurrent reads and writes to a field of a pointer to an instance of thing. Issue" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/jenkinsx/intformat/codelingo.yaml b/tenets/codelingo/jenkinsx/intformat/codelingo.yaml index 567b808d..4e3759a1 100644 --- a/tenets/codelingo/jenkinsx/intformat/codelingo.yaml +++ b/tenets/codelingo/jenkinsx/intformat/codelingo.yaml @@ -37,7 +37,7 @@ tenets: # There is an explicit tenet for each case. See more concise possible solutions below. - name: right-build-directive-wrong-file - flows: + actions: codelingo/rewrite: name: filler # non-empty rewrite section required for parse error codelingo/docs: @@ -70,7 +70,7 @@ tenets: # filename != append("packageName", "_integration_test.go") - name: right-build-directive-wrong-package - flows: + actions: codelingo/rewrite: name: | {{packageName}}_test @@ -107,7 +107,7 @@ tenets: # name != append(packageName, "_test") - name: right-package-wrong-build-directive - flows: + actions: codelingo/rewrite: name: filler # non-empty rewrite section required for parse error codelingo/docs: @@ -141,7 +141,7 @@ tenets: text == "// +build integration" - name: right-package-wrong-file - flows: + actions: codelingo/rewrite: name: filler # non-empty rewrite section required for parse error codelingo/docs: @@ -173,7 +173,7 @@ tenets: # filename != append(packageName, "_integration_test.go") - name: right-file-wrong-build-directive - flows: + actions: codelingo/rewrite: name: filler # non-empty rewrite section required for parse error codelingo/docs: @@ -208,7 +208,7 @@ tenets: text == "// +build integration" - name: right-file-wrong-package - flows: + actions: codelingo/rewrite: name: | {{packageName}}_test diff --git a/tenets/codelingo/jenkinsx/lingo_bundle.yaml b/tenets/codelingo/jenkinsx/lingo_bundle.yaml index cad3da02..b21f5209 100644 --- a/tenets/codelingo/jenkinsx/lingo_bundle.yaml +++ b/tenets/codelingo/jenkinsx/lingo_bundle.yaml @@ -1,13 +1,13 @@ description: Best practices from the jenkins-x/js repository. version: 0.0.0 tenets: - - intformat - - orginterfaces - - parallel-in-tests - - test-package-name - - tparallel +- intformat +- orginterfaces +- parallel-in-tests +- test-package-name +- tparallel tags: - - jenkins - - jenkins-x - - golang - - go \ No newline at end of file +- jenkins +- jenkins-x +- golang +- go diff --git a/tenets/codelingo/jenkinsx/orginterfaces/codelingo.yaml b/tenets/codelingo/jenkinsx/orginterfaces/codelingo.yaml index 065b2c82..e3d83c9f 100644 --- a/tenets/codelingo/jenkinsx/orginterfaces/codelingo.yaml +++ b/tenets/codelingo/jenkinsx/orginterfaces/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: orginterfaces - flows: + actions: codelingo/docs: title: IsNotInterfaceFile body: Finds interfaces which are not defined in a file called interface.go. diff --git a/tenets/codelingo/jenkinsx/parallel-in-tests/codelingo.yaml b/tenets/codelingo/jenkinsx/parallel-in-tests/codelingo.yaml index 4a03ee15..4eb257e9 100644 --- a/tenets/codelingo/jenkinsx/parallel-in-tests/codelingo.yaml +++ b/tenets/codelingo/jenkinsx/parallel-in-tests/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: parallel-in-tests - flows: + actions: codelingo/rewrite: codelingo/docs: title: Is Parallel In Tests diff --git a/tenets/codelingo/jenkinsx/tparallel/README.md b/tenets/codelingo/jenkinsx/parallel-integration-tests/README.md similarity index 100% rename from tenets/codelingo/jenkinsx/tparallel/README.md rename to tenets/codelingo/jenkinsx/parallel-integration-tests/README.md diff --git a/tenets/codelingo/jenkinsx/tparallel/auth.go b/tenets/codelingo/jenkinsx/parallel-integration-tests/auth.go similarity index 100% rename from tenets/codelingo/jenkinsx/tparallel/auth.go rename to tenets/codelingo/jenkinsx/parallel-integration-tests/auth.go diff --git a/tenets/codelingo/jenkinsx/tparallel/auth_integration_test.go b/tenets/codelingo/jenkinsx/parallel-integration-tests/auth_integration_test.go similarity index 74% rename from tenets/codelingo/jenkinsx/tparallel/auth_integration_test.go rename to tenets/codelingo/jenkinsx/parallel-integration-tests/auth_integration_test.go index 34f214ed..e711f0ac 100644 --- a/tenets/codelingo/jenkinsx/tparallel/auth_integration_test.go +++ b/tenets/codelingo/jenkinsx/parallel-integration-tests/auth_integration_test.go @@ -1,3 +1,5 @@ +// +build integration + package tparallel_test import ( @@ -8,9 +10,9 @@ import ( func TestAuthConfigParallel(t *testing.T) { t.Parallel() - auth.DoAuthStuff() + tparallel.DoAuthStuff() } func TestAuthConfig(t *testing.T) { - auth.DoAuthStuff() + tparallel.DoAuthStuff() } diff --git a/tenets/codelingo/jenkinsx/tparallel/codelingo.yaml b/tenets/codelingo/jenkinsx/parallel-integration-tests/codelingo.yaml similarity index 86% rename from tenets/codelingo/jenkinsx/tparallel/codelingo.yaml rename to tenets/codelingo/jenkinsx/parallel-integration-tests/codelingo.yaml index a297ec5d..be24d6fc 100644 --- a/tenets/codelingo/jenkinsx/tparallel/codelingo.yaml +++ b/tenets/codelingo/jenkinsx/parallel-integration-tests/codelingo.yaml @@ -1,7 +1,7 @@ tenets: # Integration test suite - name: parallel-integration-tests - flows: + actions: codelingo/docs: title: Parallel Integration Tests body: You should NOT add t.Parallel() to an unencapsulated test as it may cause intermittent failures. @@ -12,8 +12,9 @@ tenets: import codelingo/ast/go go.file(depth = any): - filename as fname - regex(/.*_integration_test.go/, fname) + go.comment_group: + go.comment: + text == "// +build integration" @review comment @rewrite "" go.call_expr(depth = any): diff --git a/tenets/codelingo/jenkinsx/parallel-integration-tests/expected.json b/tenets/codelingo/jenkinsx/parallel-integration-tests/expected.json new file mode 100644 index 00000000..93707b2d --- /dev/null +++ b/tenets/codelingo/jenkinsx/parallel-integration-tests/expected.json @@ -0,0 +1,7 @@ +[{ + "Name": "parallel-integration-tests", + "Comment": "You should NOT add t.Parallel() to an unencapsulated test as it may cause intermittent failures.", + "Filename": "auth_integration_test.go", + "Line": 12, + "Snippet": "\nfunc TestAuthConfigParallel(t *testing.T) {\n\tt.Parallel()\n\tauth.DoAuthStuff()\n}" +}] \ No newline at end of file diff --git a/tenets/codelingo/jenkinsx/test-package-name/codelingo.yaml b/tenets/codelingo/jenkinsx/test-package-name/codelingo.yaml index 83aabd20..aa92390b 100644 --- a/tenets/codelingo/jenkinsx/test-package-name/codelingo.yaml +++ b/tenets/codelingo/jenkinsx/test-package-name/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: test-package-name - flows: + actions: codelingo/rewrite: name: | {{packageName}}_test diff --git a/tenets/codelingo/jenkinsx/tparallel/expected.json b/tenets/codelingo/jenkinsx/tparallel/expected.json deleted file mode 100644 index 7670888e..00000000 --- a/tenets/codelingo/jenkinsx/tparallel/expected.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "name": "parallel-integration-tests", - "position": { - "start": { - "filename": "auth_integration_test.go", - "Offset": 163, - "Line": 10, - "Column": 2 - }, - "end": { - "filename": "auth_integration_test.go", - "Offset": 175, - "Line": 10, - "Column": 14 - } - }, - "comment": "Integration tests should not use t.Paralle()", - "ctxBefore": "\nfunc TestAuthConfigParallel(t *testing.T) {", - "lineText": "\tt.Parallel()", - "ctxAfter": "\tauth.DoAuthStuff()\n}", - "newCode": true - } - ] - \ No newline at end of file diff --git a/tenets/codelingo/juju-juju/find-typed-nil/codelingo.yaml b/tenets/codelingo/juju-juju/find-typed-nil/codelingo.yaml new file mode 100644 index 00000000..d593bbfd --- /dev/null +++ b/tenets/codelingo/juju-juju/find-typed-nil/codelingo.yaml @@ -0,0 +1,23 @@ +tenets: + - name: find-typed-nil + actions: + codelingo/review: + comment: If typed pointer is nil, return nil explicitly + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + go.func_type: + go.field_list: + go.field: + go.selector_expr: + go.ident: + name == "lxdprofile" + go.ident: + name == "LXDProfile" + @review comment + go.return_stmt(depth = any): + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == "LXDProfile" diff --git a/tenets/codelingo/juju-juju/lingo_bundle.yaml b/tenets/codelingo/juju-juju/lingo_bundle.yaml new file mode 100644 index 00000000..5914a852 --- /dev/null +++ b/tenets/codelingo/juju-juju/lingo_bundle.yaml @@ -0,0 +1,9 @@ +description: Tenets written for juju-juju +version: 0.0.0 +tenets: +- find-typed-nil +- unmanaged-goroutine-in-worker +tags: +- go +- juju + diff --git a/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/codelingo.yaml b/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/codelingo.yaml new file mode 100644 index 00000000..7a844b25 --- /dev/null +++ b/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/codelingo.yaml @@ -0,0 +1,42 @@ +tenets: +- name: unmanaged-goroutine-in-worker + actions: + codelingo/docs: + title: Unmanaged goroutine in worker + body: | + Implementations of worker.Worker should guarantee that they're no longer doing work or + consuming resources once Wait returns. + + Starting a goroutine directly makes it much harder to fulfil this requirement; consider + using Tomb.Go or Catacomb.Add (with NewSimpleWorker) to ensure that all work is completed + and all errors are handled. (Constructs from package sync may also serve, but they are + harder to verify correct, especially as code evolves, and are therefore not preferred.) + codelingo/review: + comment: | + {{packageName}}.{{typeName}} is a worker.Worker which starts an unmanaged goroutine. + query: | + import codelingo/ast/gotypes + + gotypes.package: + name as packageName + gotypes.named: + name as typeName + + # Kill and Wait to identify Worker. + gotypes.method: + edge("reference"): + gotypes.func: + name == "Kill" + gotypes.method: + edge("reference"): + gotypes.func: + name == "Wait" + + # Any method that directly invokes a goroutine. + gotypes.method: + edge("reference"): + gotypes.func: + edge("link"): + gotypes.func_decl: + @review comment + gotypes.go_stmt(depth=any) diff --git a/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/expected.json b/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/expected.json new file mode 100644 index 00000000..1cc36bf5 --- /dev/null +++ b/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/expected.json @@ -0,0 +1,6 @@ +[{ + "Filename": "sample.go", + "Line": 7, + "Comment": "sample.W is a Worker which starts an unmanaged goroutine.\n\nImplementations of worker.Worker should guarantee that they're no longer doing work or\nconsuming resources once Wait returns.\n\nStarting a goroutine directly makes it much harder to fulfil this requirement; consider\nusing Tomb.Go or Catacomb.Add (with NewSimpleWorker) to ensure that all work is completed\nand all errors are handled. (Constructs from package sync may also serve, but they are\nharder to verify correct, especially as code evolves, and are therefore not preferred.)\n", + "Snippet": "func (W) Kill() {}\nfunc (W) Wait() error { return nil }\nfunc (W) ohNo() { go func() {}() }\n\ntype X struct{}" +}] diff --git a/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/sample.go b/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/sample.go new file mode 100644 index 00000000..363d7db0 --- /dev/null +++ b/tenets/codelingo/juju-juju/unmanaged-goroutine-in-worker/sample.go @@ -0,0 +1,13 @@ +package sample + +type W struct{} + +func (W) Kill() {} +func (W) Wait() error { return nil } +func (W) ohNo() { go func() {}() } + +type X struct{} + +func (X) Krill() {} +func (X) White() error { return nil } +func (X) ohYes() { go func() {}() } diff --git a/tenets/codelingo/kubernetes/flags-have-underscores/codelingo.yaml b/tenets/codelingo/kubernetes/flags-have-underscores/codelingo.yaml index 55bce09f..2ff7a3bf 100644 --- a/tenets/codelingo/kubernetes/flags-have-underscores/codelingo.yaml +++ b/tenets/codelingo/kubernetes/flags-have-underscores/codelingo.yaml @@ -8,7 +8,7 @@ funcs: tenets: - name: flags-have-underscores - flows: + actions: codelingo/docs: title: Flags Have Underscores body: | diff --git a/tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/codelingo.yaml b/tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/codelingo.yaml index 23c765d3..ed46bc8b 100644 --- a/tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/codelingo.yaml +++ b/tenets/codelingo/kubernetes/jsonapi-kind-compulsory-metadata/codelingo.yaml @@ -19,7 +19,7 @@ funcs: } tenets: - name: jsonapi-kind-compulsory-metadata - flows: + actions: codelingo/docs: title: JSON API kind compulsory metadata body: | diff --git a/tenets/codelingo/kubernetes/lingo_bundle.yaml b/tenets/codelingo/kubernetes/lingo_bundle.yaml index 354c0dbd..aba8582b 100644 --- a/tenets/codelingo/kubernetes/lingo_bundle.yaml +++ b/tenets/codelingo/kubernetes/lingo_bundle.yaml @@ -1,19 +1,16 @@ description: Best practices from the kubernetes repository. version: 0.0.0 tenets: - - flags-have-underscores - - jsonapi-kind-compulsory-metadata - - looped-vars-outside-loop - - misused-nil-interface - - new-package-requires-test - - shadowed-func-parameter - - well-formed-lists - - well-named-interface - - well-named-lists - - well-named-lock - - well-named-locks - - well-named-package +- flags-have-underscores +- jsonapi-kind-compulsory-metadata +- new-package-requires-test +- well-formed-lists +- well-named-interface +- well-named-lists +- well-named-lock +- well-named-locks +- well-named-package tags: - - kubernetes - - golang - - go \ No newline at end of file +- kubernetes +- golang +- go diff --git a/tenets/codelingo/kubernetes/new-package-requires-test/codelingo.yaml b/tenets/codelingo/kubernetes/new-package-requires-test/codelingo.yaml index 9e02fdf0..babb9f75 100644 --- a/tenets/codelingo/kubernetes/new-package-requires-test/codelingo.yaml +++ b/tenets/codelingo/kubernetes/new-package-requires-test/codelingo.yaml @@ -13,7 +13,7 @@ funcs: } tenets: - name: new-package-requires-test - flows: + actions: codelingo/docs: title: New Package Requires Test body: | diff --git a/tenets/codelingo/kubernetes/well-formed-lists/codelingo.yaml b/tenets/codelingo/kubernetes/well-formed-lists/codelingo.yaml index af5e8371..f1c77705 100644 --- a/tenets/codelingo/kubernetes/well-formed-lists/codelingo.yaml +++ b/tenets/codelingo/kubernetes/well-formed-lists/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: well-formed-lists - flows: + actions: codelingo/docs: title: Well Formed Lists body: | diff --git a/tenets/codelingo/kubernetes/well-named-interface/codelingo.yaml b/tenets/codelingo/kubernetes/well-named-interface/codelingo.yaml index dcd0d5cc..43b84369 100644 --- a/tenets/codelingo/kubernetes/well-named-interface/codelingo.yaml +++ b/tenets/codelingo/kubernetes/well-named-interface/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: well-named-interface - flows: + actions: codelingo/docs: title: Well Named Interface body: | diff --git a/tenets/codelingo/kubernetes/well-named-lists/codelingo.yaml b/tenets/codelingo/kubernetes/well-named-lists/codelingo.yaml index 336fb392..df3bc835 100644 --- a/tenets/codelingo/kubernetes/well-named-lists/codelingo.yaml +++ b/tenets/codelingo/kubernetes/well-named-lists/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: well-named-lists - flows: + actions: codelingo/docs: title: Well Named Lists body: | diff --git a/tenets/codelingo/kubernetes/well-named-lock/codelingo.yaml b/tenets/codelingo/kubernetes/well-named-lock/codelingo.yaml index d80a7129..2beba2d9 100644 --- a/tenets/codelingo/kubernetes/well-named-lock/codelingo.yaml +++ b/tenets/codelingo/kubernetes/well-named-lock/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: well-named-lock - flows: + actions: codelingo/docs: title: Well Named Lock body: | diff --git a/tenets/codelingo/kubernetes/well-named-locks/codelingo.yaml b/tenets/codelingo/kubernetes/well-named-locks/codelingo.yaml index 2a41e620..5528f13d 100644 --- a/tenets/codelingo/kubernetes/well-named-locks/codelingo.yaml +++ b/tenets/codelingo/kubernetes/well-named-locks/codelingo.yaml @@ -7,7 +7,7 @@ funcs: } tenets: - name: well-named-locks - flows: + actions: codelingo/docs: title: Well Named Locks body: | diff --git a/tenets/codelingo/kubernetes/well-named-package/codelingo.yaml b/tenets/codelingo/kubernetes/well-named-package/codelingo.yaml index 3bf9bc6e..b45d24fa 100644 --- a/tenets/codelingo/kubernetes/well-named-package/codelingo.yaml +++ b/tenets/codelingo/kubernetes/well-named-package/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: well-named-package - flows: + actions: codelingo/docs: title: Well Named Package body: | diff --git a/tenets/codelingo/landmines/lingo_bundle.yaml b/tenets/codelingo/landmines/lingo_bundle.yaml new file mode 100644 index 00000000..e55b6f69 --- /dev/null +++ b/tenets/codelingo/landmines/lingo_bundle.yaml @@ -0,0 +1,9 @@ +description: Best practices from the The Three Go Landmines gist. +version: 0.0.0 +tenets: +- looped-vars-outside-loop +- misused-nil-interface +- shadowed-func-parameter +tags: +- golang +- go diff --git a/tenets/codelingo/kubernetes/looped-vars-outside-loop/README.md b/tenets/codelingo/landmines/looped-vars-outside-loop/README.md similarity index 100% rename from tenets/codelingo/kubernetes/looped-vars-outside-loop/README.md rename to tenets/codelingo/landmines/looped-vars-outside-loop/README.md diff --git a/tenets/codelingo/kubernetes/looped-vars-outside-loop/codelingo.yaml b/tenets/codelingo/landmines/looped-vars-outside-loop/codelingo.yaml similarity index 99% rename from tenets/codelingo/kubernetes/looped-vars-outside-loop/codelingo.yaml rename to tenets/codelingo/landmines/looped-vars-outside-loop/codelingo.yaml index a1d4f95d..c1c5cc59 100644 --- a/tenets/codelingo/kubernetes/looped-vars-outside-loop/codelingo.yaml +++ b/tenets/codelingo/landmines/looped-vars-outside-loop/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: looped-vars-outside-loop - flows: + actions: codelingo/docs: title: Looped vars outside loop body: | diff --git a/tenets/codelingo/kubernetes/looped-vars-outside-loop/example.go b/tenets/codelingo/landmines/looped-vars-outside-loop/example.go similarity index 100% rename from tenets/codelingo/kubernetes/looped-vars-outside-loop/example.go rename to tenets/codelingo/landmines/looped-vars-outside-loop/example.go diff --git a/tenets/codelingo/kubernetes/looped-vars-outside-loop/expected.json b/tenets/codelingo/landmines/looped-vars-outside-loop/expected.json similarity index 100% rename from tenets/codelingo/kubernetes/looped-vars-outside-loop/expected.json rename to tenets/codelingo/landmines/looped-vars-outside-loop/expected.json diff --git a/tenets/codelingo/kubernetes/misused-nil-interface/README.md b/tenets/codelingo/landmines/misused-nil-interface/README.md similarity index 100% rename from tenets/codelingo/kubernetes/misused-nil-interface/README.md rename to tenets/codelingo/landmines/misused-nil-interface/README.md diff --git a/tenets/codelingo/kubernetes/misused-nil-interface/codelingo.yaml b/tenets/codelingo/landmines/misused-nil-interface/codelingo.yaml similarity index 99% rename from tenets/codelingo/kubernetes/misused-nil-interface/codelingo.yaml rename to tenets/codelingo/landmines/misused-nil-interface/codelingo.yaml index 2ffc644f..f5da37db 100644 --- a/tenets/codelingo/kubernetes/misused-nil-interface/codelingo.yaml +++ b/tenets/codelingo/landmines/misused-nil-interface/codelingo.yaml @@ -13,7 +13,7 @@ funcs: } tenets: - name: misused-nil-interface - flows: + actions: codelingo/docs: title: Misused Nil Interface body: | diff --git a/tenets/codelingo/kubernetes/misused-nil-interface/example.go b/tenets/codelingo/landmines/misused-nil-interface/example.go similarity index 100% rename from tenets/codelingo/kubernetes/misused-nil-interface/example.go rename to tenets/codelingo/landmines/misused-nil-interface/example.go diff --git a/tenets/codelingo/kubernetes/misused-nil-interface/expected.json b/tenets/codelingo/landmines/misused-nil-interface/expected.json similarity index 100% rename from tenets/codelingo/kubernetes/misused-nil-interface/expected.json rename to tenets/codelingo/landmines/misused-nil-interface/expected.json diff --git a/tenets/codelingo/kubernetes/shadowed-func-parameter/README.md b/tenets/codelingo/landmines/shadowed-func-parameter/README.md similarity index 100% rename from tenets/codelingo/kubernetes/shadowed-func-parameter/README.md rename to tenets/codelingo/landmines/shadowed-func-parameter/README.md diff --git a/tenets/codelingo/kubernetes/shadowed-func-parameter/codelingo.yaml b/tenets/codelingo/landmines/shadowed-func-parameter/codelingo.yaml similarity index 98% rename from tenets/codelingo/kubernetes/shadowed-func-parameter/codelingo.yaml rename to tenets/codelingo/landmines/shadowed-func-parameter/codelingo.yaml index 3d398b81..a5cd1741 100644 --- a/tenets/codelingo/kubernetes/shadowed-func-parameter/codelingo.yaml +++ b/tenets/codelingo/landmines/shadowed-func-parameter/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: shadowed-func-parameter - flows: + actions: codelingo/docs: title: Shadowed Function Parameter body: | diff --git a/tenets/codelingo/kubernetes/shadowed-func-parameter/example.go b/tenets/codelingo/landmines/shadowed-func-parameter/example.go similarity index 100% rename from tenets/codelingo/kubernetes/shadowed-func-parameter/example.go rename to tenets/codelingo/landmines/shadowed-func-parameter/example.go diff --git a/tenets/codelingo/kubernetes/shadowed-func-parameter/expected.json b/tenets/codelingo/landmines/shadowed-func-parameter/expected.json similarity index 100% rename from tenets/codelingo/kubernetes/shadowed-func-parameter/expected.json rename to tenets/codelingo/landmines/shadowed-func-parameter/expected.json diff --git a/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/codelingo.yaml b/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/codelingo.yaml index 1e927b27..464e9e5e 100644 --- a/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/codelingo.yaml +++ b/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/codelingo.yaml @@ -19,7 +19,7 @@ funcs: } tenets: - name: exported-package-is-not-tested - flows: + actions: codelingo/docs: title: Exported package is tested body: | diff --git a/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/test/my_test.go b/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/test/my_test.go index edaab5b5..f9402a18 100644 --- a/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/test/my_test.go +++ b/tenets/codelingo/lightning-network-daemon/exported-package-is-tested/test/my_test.go @@ -1,7 +1,7 @@ package testpackage -import "github.com/lightningnetwork/lnd/goodpack" +import "github.com/lightningnetwork/lnd/channeldb" import ( - "github.com/lightningnetwork/lnd/goodpack" + "github.com/lightningnetwork/lnd/channeldb" ) diff --git a/tenets/codelingo/lightning-network-daemon/lingo_bundle.yaml b/tenets/codelingo/lightning-network-daemon/lingo_bundle.yaml index 9f56803c..abf9a702 100644 --- a/tenets/codelingo/lightning-network-daemon/lingo_bundle.yaml +++ b/tenets/codelingo/lightning-network-daemon/lingo_bundle.yaml @@ -1,9 +1,9 @@ description: Best practices from the lightning network daemon repository. version: 0.0.0 tenets: - - rpc-responses-display-uniformly - - exported-package-is-tested +- exported-package-is-tested +- rpc-responses-display-uniformly tags: - - lnd - - golang - - go \ No newline at end of file +- lnd +- golang +- go diff --git a/tenets/codelingo/lightning-network-daemon/rpc-responses-display-uniformly/codelingo.yaml b/tenets/codelingo/lightning-network-daemon/rpc-responses-display-uniformly/codelingo.yaml index f7de1787..dc895184 100644 --- a/tenets/codelingo/lightning-network-daemon/rpc-responses-display-uniformly/codelingo.yaml +++ b/tenets/codelingo/lightning-network-daemon/rpc-responses-display-uniformly/codelingo.yaml @@ -25,7 +25,7 @@ funcs: } tenets: - name: rpc-responses-display-uniformly - flows: + actions: codelingo/docs: title: RPC responses display uniformly body: | diff --git a/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/codelingo.yaml b/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/codelingo.yaml new file mode 100644 index 00000000..63b49ef5 --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/codelingo.yaml @@ -0,0 +1,20 @@ +tenets: + - name: assignment-to-nil-map + actions: + codelingo/review: + comment: Use 'make' when creating maps to prevent assigning to a nil map. + codelingo/rewrite: + query: | + import codelingo/ast/go + @rewrite --replace "{{mapName}} := make(map[{{keyType}}]{{valType}})" + go.gen_decl(depth = any): + @review comment + go.value_spec: + go.names: + go.ident: + name as mapName + go.map_type: + go.ident: + name as keyType + go.ident: + name as valType diff --git a/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/expected.json b/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/expected.json new file mode 100644 index 00000000..a3d829dc --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Use 'make' when creating maps to prevent assigning to a nil map.", + "Filename": "main.go", + "Line": 9, + "Snippet": "\tmetrics := []string{\"example\"}\n\n\tvar accessTimes map[string]int64\n\taccessTimes = make(map[string]int64)\n\tfor _, m := range metrics {" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/main.go b/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/main.go new file mode 100644 index 00000000..3bd065bb --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/assignment-to-nil-map/main.go @@ -0,0 +1,13 @@ +package main + +import "time" + +func main() { + now := time.Now().Unix() + metrics := []string{"example"} + + var accessTimes map[string]int64 // ISSUE + for _, m := range metrics { + accessTimes[m] = now + } +} diff --git a/tenets/codelingo/lomik-go-carbon/lingo_bundle.yaml b/tenets/codelingo/lomik-go-carbon/lingo_bundle.yaml new file mode 100644 index 00000000..c5388df8 --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/lingo_bundle.yaml @@ -0,0 +1,9 @@ +description: Tenets written for lomik-go-carbon +version: 0.0.0 +tenets: +- assignment-to-nil-map +- missing-stop-ticker +tags: +- go +- lomik +- go-carbon diff --git a/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/codelingo.yaml b/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/codelingo.yaml new file mode 100644 index 00000000..7d372e67 --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/codelingo.yaml @@ -0,0 +1,29 @@ +tenets: + - name: missing-stop-ticker + actions: + codelingo/review: + comment: Stop the ticker to release associated resources. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + @review comment + go.assign_stmt(depth = any): + go.lhs: + go.ident: + sibling_order == 0 + name as varName + go.rhs: + go.call_expr: + go.selector_expr: + go.ident: + name == "time" + go.ident: + name == "NewTicker" + exclude: + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == varName + go.ident: + name == "Stop" diff --git a/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/expected.json b/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/expected.json new file mode 100644 index 00000000..639ef43c --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Stop the ticker to release associated resources.", + "Filename": "test.go", + "Line": 8, + "Snippet": "func main() {\n\n ticker := time.NewTicker(500 * time.Millisecond)\n go func() {\n for t := range ticker.C {" + } +] \ No newline at end of file diff --git a/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/test.go b/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/test.go new file mode 100644 index 00000000..2548f53e --- /dev/null +++ b/tenets/codelingo/lomik-go-carbon/missing-stop-ticker/test.go @@ -0,0 +1,18 @@ +package main + +import "time" +import "fmt" + +func main() { + + ticker := time.NewTicker(500 * time.Millisecond) + go func() { + for t := range ticker.C { + fmt.Println("Tick at", t) + } + }() + + time.Sleep(1600 * time.Millisecond) + //ticker.Stop() + fmt.Println("Ticker stopped") +} diff --git a/tenets/codelingo/modica/lingo_bundle.yaml b/tenets/codelingo/modica/lingo_bundle.yaml index ea10cde0..3199082f 100644 --- a/tenets/codelingo/modica/lingo_bundle.yaml +++ b/tenets/codelingo/modica/lingo_bundle.yaml @@ -1,7 +1,5 @@ description: Tenets written for Modica version: 0.0.0 tenets: - - null-check - - transport tags: - - php \ No newline at end of file +- php diff --git a/tenets/codelingo/modica/null-check/codelingo.yaml b/tenets/codelingo/modica/null-check/codelingo.yaml deleted file mode 100644 index 5218284e..00000000 --- a/tenets/codelingo/modica/null-check/codelingo.yaml +++ /dev/null @@ -1,21 +0,0 @@ -tenets: - - name: null-check - flows: - codelingo/docs: - title: Null Check - body: Finds all calls to null-check - codelingo/review: - comment: Please use `=== null` rather than `is_null` - query: | - import codelingo/ast/php - - php.file(depth = any): - any_of: - @review comment - php.expr_funccall(depth = any): - php.name_fullyqualified: - name == "is_null" - @review comment - php.expr_funccall(depth = any): - php.name: - name == "is_null" diff --git a/tenets/codelingo/modica/transport/README.md b/tenets/codelingo/modica/transport/README.md deleted file mode 100644 index 8e016f5e..00000000 --- a/tenets/codelingo/modica/transport/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# transport - -_by modica, part of their default bundle_ - - -[Load up in the codelingo.io/playground](https://codelingo.io/playground/?repo=github.com/codelingo/hub&dir=tenets/modica/default/transport&tenet=modica/default/transport) \ No newline at end of file diff --git a/tenets/codelingo/modica/transport/codelingo.yaml b/tenets/codelingo/modica/transport/codelingo.yaml deleted file mode 100644 index b176a8ff..00000000 --- a/tenets/codelingo/modica/transport/codelingo.yaml +++ /dev/null @@ -1,113 +0,0 @@ -tenets: - - name: transport-template - query: | - import codelingo/ast/php - - php.dir(depth = any): - php.file: - @filter filename - filename - php.stmt_namespace: - php.name: - @filter namespace - name - php.stmt_class: - @filter controller - name - @review comment - php.stmt_classmethod: - @filter action - name - flows: - codelingo/docs: - title: Transport Template - body: Find transport controllers and actions that violate naming conventions - codelingo/review: - comment: | - Controller class and method name in files named '//.php', should match : - - namespace Omni\Controller\; - class Controller extends Controller { - public function Action() {} - } - codelingo/filter: - language: go - body: | - package main - - import ( - "errors" - "regexp" - "strings" - ) - - func Filter(results map[string]interface{}) (bool, error) { - filename, ok := results["name"].(string) - if !ok { - return false, errors.New("No filename value") - } - - ns, ok := results["namespace"].(string) - if !ok { - return false, errors.New("No namespace value") - } - - // Namespace stored with extra slash - parts := strings.Split(ns, "\\\\") - namespace := parts[len(parts)-1] - - controller, ok := results["controller"].(string) - if !ok { - return false, errors.New("No controller value") - } - - action, ok := results["action"].(string) - if !ok { - return false, errors.New("No action value") - } - - filenameRegex := regexp.MustCompile(`.*/([_a-z]*)/([_a-z]*)/([_a-z]*).php`) - filenameMatch := filenameRegex.FindStringSubmatch(filename) - if len(filenameMatch) == 0 { - return false, errors.New("Failed filename match") - } - - namespaceRegex := regexp.MustCompile(`\\([a-zA-Z]*)$`) - namespaceMatch := namespaceRegex.FindStringSubmatch(namespace) - if len(namespaceMatch) == 0 { - return false, errors.New("Failed namespace match") - } - - controllerRegex := regexp.MustCompile(`^([a-zA-Z]*)Controller$`) - controllerMatch := controllerRegex.FindStringSubmatch(controller) - if len(controllerMatch) == 0 { - return false, errors.New("Failed controller match") - } - - - actionRegex := regexp.MustCompile(`^([a-z][a-zA-Z]*)Action$`) - actionMatch := actionRegex.FindStringSubmatch(action) - if len(actionMatch) == 0 { - return false, errors.New("Failed action match") - } - - - if !snakequal(filenameMatch[1], namespaceMatch[1]) { - return true, nil - } - - if !snakequal(filenameMatch[2], controllerMatch[1]) { - return true, nil - } - - if !snakequal(filenameMatch[3], actionMatch[1]) { - return true, nil - } - - return false, nil - } - - func snakequal(snake, camel string) bool { - cutSnake := strings.Replace(snake, "_", "", -1) - return strings.EqualFold(cutSnake, camel) - } diff --git a/tenets/codelingo/modica/transport/made_up/something.php b/tenets/codelingo/modica/transport/made_up/something.php deleted file mode 100644 index af476b69..00000000 --- a/tenets/codelingo/modica/transport/made_up/something.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/tenets/codelingo/modica/transport/telco_networks/list.php b/tenets/codelingo/modica/transport/telco_networks/list.php deleted file mode 100644 index e6d51ddc..00000000 --- a/tenets/codelingo/modica/transport/telco_networks/list.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/tenets/codelingo/modica/transport/wrong_action_name/something.php b/tenets/codelingo/modica/transport/wrong_action_name/something.php deleted file mode 100644 index e0f5e042..00000000 --- a/tenets/codelingo/modica/transport/wrong_action_name/something.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/tenets/codelingo/modica/transport/wrong_controller_name/something.php b/tenets/codelingo/modica/transport/wrong_controller_name/something.php deleted file mode 100644 index 5e8957e5..00000000 --- a/tenets/codelingo/modica/transport/wrong_controller_name/something.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/tenets/codelingo/modica/transport/wrong_filename/asdf.php b/tenets/codelingo/modica/transport/wrong_filename/asdf.php deleted file mode 100644 index 217c73a5..00000000 --- a/tenets/codelingo/modica/transport/wrong_filename/asdf.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/tenets/codelingo/modica/transport/wrong_namespace/something.php b/tenets/codelingo/modica/transport/wrong_namespace/something.php deleted file mode 100644 index de5a8507..00000000 --- a/tenets/codelingo/modica/transport/wrong_namespace/something.php +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/codelingo.yaml b/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/codelingo.yaml new file mode 100644 index 00000000..73ee3fe5 --- /dev/null +++ b/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/codelingo.yaml @@ -0,0 +1,47 @@ +tenets: + - name: always-close-object-response-body + actions: + codelingo/review: + comment: Close the response when you are done to recycle connections. + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + @review comment + go.assign_stmt(depth = any): + go.lhs: + go.ident: + sibling_order == 0 + name as varName + go.rhs: + go.call_expr: + go.selector_expr: + go.selector_expr: + go.ident + go.ident: + name == "Object" + any_of: + go.ident: + name == "Get" + go.ident: + name == "Put" + go.ident: + name == "Head" + go.ident: + name == "Append" + go.ident: + name == "UploadPart" + go.ident: + name == "Delete" + go.ident: + name == "Options" + exclude: + go.call_expr(depth = any): + go.selector_expr: + go.selector_expr: + go.ident: + name == varName + go.ident: + name == "Body" + go.ident: + name == "Close" diff --git a/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/expected.json b/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/expected.json new file mode 100755 index 00000000..95605cbf --- /dev/null +++ b/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Close the response when you are done to recycle connections.", + "Filename": "main.go", + "Line": 13, + "Snippet": "func main() {\n\tvar c client\n\t_, err := c.Object.Put()\n\tif err != nil {\n\t\tpanic(err)" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/main.go b/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/main.go new file mode 100644 index 00000000..f44e9383 --- /dev/null +++ b/tenets/codelingo/mozillazg-go-cos/always-close-object-response-body/main.go @@ -0,0 +1,22 @@ +package main + +import "fmt" + +type object int + +type client struct { + Object object +} + +func main() { + var c client + _, err := c.Object.Put() // ISSUE + if err != nil { + panic(err) + } +} + +func (o *object) Put() (int, error) { + fmt.Println("put") + return 1, nil +} diff --git a/tenets/codelingo/mozillazg-go-cos/lingo_bundle.yaml b/tenets/codelingo/mozillazg-go-cos/lingo_bundle.yaml new file mode 100644 index 00000000..790a4e36 --- /dev/null +++ b/tenets/codelingo/mozillazg-go-cos/lingo_bundle.yaml @@ -0,0 +1,9 @@ +description: Tenets written for mozillazg/go-cos. +version: 0.0.0 +tenets: +- always-close-object-response-body +tags: +- golang +- go +- go-cos +- mozillazg diff --git a/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/codelingo.yaml b/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/codelingo.yaml new file mode 100644 index 00000000..fbf33ba8 --- /dev/null +++ b/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/codelingo.yaml @@ -0,0 +1,92 @@ +tenets: + - name: find-funcs + actions: + codelingo/review: + comment: | + Replace this `if` statement with an `else if`. it is mutually exclusive with the previous if statement has the same operands. + query: | + import codelingo/ast/go + + go.element(depth = any): + go.if_stmt: + sibling_order as so + go.binary_expr: + op as operator1 + go.ident: + name as lhsVariable + go.basic_lit: + value as rhsLiteral + @review comment + go.if_stmt: + sibling_order as so2 + oneGreater(so, so2) # TODO: sibling_order == increment(so) + go.binary_expr: + op as operator2 + doesNotOverlap(operator1, operator2) + go.ident: + name == lhsVariable + go.basic_lit: + value == rhsLiteral + +funcs: + - name: increment + type: resolver + body: | + function (a) { + return a + 1 + } + - name: oneGreater + type: asserter + body: | + function (a, b) { + return a + 1 == b + } + - name: doesNotOverlap + type: asserter + # overlap would return whether the two conditions like x >= 1 and x <= 1 can be true at the same time. + # doesNotOverlap does the inverse + # + # More precisely, overlap would take two operators op1 and op2 (like < or >=), and returns whether there + # exists an m and n such that (m op1 n) && (m op2 n). + # TODO, generalise for a given n1 and n2 + body: | + function (op1, op2) { + // from https://stackoverflow.com/a/16436975 + function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + + for (var i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; + } + + // ES5 hack from https://stackoverflow.com/a/237176 + Array.prototype.includesArray = function(obj) { + var i = this.length; + while (i--) { + if (arraysEqual(this[i], obj)) { + return true; + } + } + return false; + } + + var overlapping = [ + [">", ">="], + [">=", "=="], + [">=", "<="], + ["==", "<="], + ["<=", "<"], + // Inverse + [">=", ">"], + ["==", ">="], + ["<=", ">="], + ["<=", "=="], + ["<", "<="], + ] + + return !(overlapping.includesArray([op1, op2]) || overlapping.includesArray([op2, op1])) + } \ No newline at end of file diff --git a/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/expected.json b/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/expected.json new file mode 100755 index 00000000..e0d9d37f --- /dev/null +++ b/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Replace this `if` statement with an `else if`. it is mutually exclusive with the previous if statement has the same operands.\n", + "Filename": "main.go", + "Line": 12, + "Snippet": "\t\tfmt.Println(a)\n\t}\n\tif a \u003e 1 { // ISSUE\n\t\tfmt.Println(a)\n\t}\n\n\t// Non issue as == and \u003e= are not mutually exclusive" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/main.go b/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/main.go new file mode 100644 index 00000000..ab0a2ac1 --- /dev/null +++ b/tenets/codelingo/mssola-user_agent/else-if-when-mutually-exclusive/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" +) + +func main() { + a := 1 + if a == 1 { + fmt.Println(a) + } + if a > 1 { // ISSUE + fmt.Println(a) + } + + // Non issue as == and >= are not mutually exclusive + b := 1 + if b == 1 { + fmt.Println(b) + } + if b >= 1 { + fmt.Println(b) + } + + // Non issue as rhs variables are different + c := 1 + if c == 1 { + fmt.Println(c) + } + if c > 12 { + fmt.Println(c) + } + + // Non issue as lhs variables are different + d := 1 + if b == 1 { + fmt.Println(d) + } + if d > 1 { + fmt.Println(d) + } + + // Non issues as the second if is already nested in an else + e := 1 + if e == 1 { + fmt.Println(e) + } else if e > 1 { + fmt.Println(e) + } +} \ No newline at end of file diff --git a/tenets/codelingo/mssola-user_agent/lingo_bundle.yaml b/tenets/codelingo/mssola-user_agent/lingo_bundle.yaml new file mode 100644 index 00000000..71f9ec9d --- /dev/null +++ b/tenets/codelingo/mssola-user_agent/lingo_bundle.yaml @@ -0,0 +1,8 @@ +description: Tenets written for mssola-user_agent +version: 0.0.0 +tenets: +- else-if-when-mutually-exclusive +tags: +- go +- mssola +- user_agent diff --git a/tenets/codelingo/php/boolean-argument/codelingo.yaml b/tenets/codelingo/php/boolean-argument/codelingo.yaml index a74c1132..476786e3 100644 --- a/tenets/codelingo/php/boolean-argument/codelingo.yaml +++ b/tenets/codelingo/php/boolean-argument/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: boolean-argument - flows: + actions: codelingo/docs: title: Boolean Argument body: A boolean flag argument is a reliable indicator for a violation of the Single Responsibility Principle (SRP). https://github.com/phpmd/phpmd/blob/master/src/site/rst/rules/index.rst#clean-code-rules diff --git a/tenets/codelingo/php/count-in-loop/codelingo.yaml b/tenets/codelingo/php/count-in-loop/codelingo.yaml index a84f0565..2964582a 100644 --- a/tenets/codelingo/php/count-in-loop/codelingo.yaml +++ b/tenets/codelingo/php/count-in-loop/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: count-in-loop - flows: + actions: codelingo/docs: title: Count In Loop body: Using count/sizeof in loops expressions is considered bad practice https://github.com/phpmd/phpmd/blob/master/src/site/rst/rules/design.rst#countinloopexpression. diff --git a/tenets/codelingo/php/empty-catch-block/codelingo.yaml b/tenets/codelingo/php/empty-catch-block/codelingo.yaml index 2852b8cc..258550da 100644 --- a/tenets/codelingo/php/empty-catch-block/codelingo.yaml +++ b/tenets/codelingo/php/empty-catch-block/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: empty-catch-block - flows: + actions: codelingo/docs: title: Empty Catch Block body: | @@ -12,4 +12,4 @@ tenets: @review comment php.stmt_catch(depth = any): - child_count == "1" + child_count == 1 diff --git a/tenets/codelingo/php/eval-expression/codelingo.yaml b/tenets/codelingo/php/eval-expression/codelingo.yaml index 7be1639f..dc5715a1 100644 --- a/tenets/codelingo/php/eval-expression/codelingo.yaml +++ b/tenets/codelingo/php/eval-expression/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: eval-expression - flows: + actions: codelingo/docs: title: Eval Expression body: | diff --git a/tenets/codelingo/php/if-assignment/codelingo.yaml b/tenets/codelingo/php/if-assignment/codelingo.yaml index 3e14af8e..6fe0bef3 100644 --- a/tenets/codelingo/php/if-assignment/codelingo.yaml +++ b/tenets/codelingo/php/if-assignment/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: if-assignment - flows: + actions: codelingo/docs: title: If Assignment body: | diff --git a/tenets/codelingo/php/lingo_bundle.yaml b/tenets/codelingo/php/lingo_bundle.yaml index ad32c5d0..bb11b6ec 100644 --- a/tenets/codelingo/php/lingo_bundle.yaml +++ b/tenets/codelingo/php/lingo_bundle.yaml @@ -1,13 +1,15 @@ description: Best practices for PHP. version: 0.0.0 tenets: - - boolean-argument - - count-in-loop - - empty-catch-block - - eval-expression - - if-assignment - - offsite-redirection - - phplint - - sql-concats +- boolean-argument +- count-in-loop +- empty-catch-block +- eval-expression +- if-assignment +- null-check +- offsite-redirection +- phplint +- shorthand-assignment-operator +- unused-private-functions tags: - - php \ No newline at end of file +- php diff --git a/tenets/codelingo/modica/null-check/README.md b/tenets/codelingo/php/null-check/README.md similarity index 100% rename from tenets/codelingo/modica/null-check/README.md rename to tenets/codelingo/php/null-check/README.md diff --git a/tenets/codelingo/php/null-check/codelingo.yaml b/tenets/codelingo/php/null-check/codelingo.yaml new file mode 100644 index 00000000..3af98f7d --- /dev/null +++ b/tenets/codelingo/php/null-check/codelingo.yaml @@ -0,0 +1,18 @@ +tenets: + - name: null-check + actions: + codelingo/docs: + title: Null Check + body: Finds all calls to null-check + codelingo/review: + comment: Please use `=== null` rather than `is_null` + query: | + import codelingo/ast/php + + @review comment + php.expr_funccall(depth = any): + any_of: + php.name_fullyqualified: + name == "is_null" + php.name: + name == "is_null" diff --git a/tenets/codelingo/modica/null-check/example.php b/tenets/codelingo/php/null-check/example.php similarity index 100% rename from tenets/codelingo/modica/null-check/example.php rename to tenets/codelingo/php/null-check/example.php diff --git a/tenets/codelingo/php/null-check/expected.json b/tenets/codelingo/php/null-check/expected.json new file mode 100755 index 00000000..a00e3b56 --- /dev/null +++ b/tenets/codelingo/php/null-check/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Please use `=== null` rather than `is_null`", + "Filename": "example.php", + "Line": 4, + "Snippet": "\n$foo = NULL;\nvar_dump(is_null($foo));\n\nvar_dump(!is_null($foo));" + }, + { + "Comment": "Please use `=== null` rather than `is_null`", + "Filename": "example.php", + "Line": 6, + "Snippet": "var_dump(is_null($foo));\n\nvar_dump(!is_null($foo));\n\nvar_dump($foo === NULL);" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/php/offsite-redirection/codelingo.yaml b/tenets/codelingo/php/offsite-redirection/codelingo.yaml index 1b64627b..e1cb50ea 100644 --- a/tenets/codelingo/php/offsite-redirection/codelingo.yaml +++ b/tenets/codelingo/php/offsite-redirection/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: use-offsite-check - flows: + actions: codelingo/docs: title: Use Offsite Check body: | diff --git a/tenets/codelingo/php/phplint/codelingo.yaml b/tenets/codelingo/php/phplint/codelingo.yaml index 6f27d576..d05b0a5a 100644 --- a/tenets/codelingo/php/phplint/codelingo.yaml +++ b/tenets/codelingo/php/phplint/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: php-lint-rules - flows: + actions: codelingo/docs: title: Php Lint Rules body: Example tenet that finds all lint problems. diff --git a/tenets/codelingo/php/shorthand-assignment-operator/codelingo.yaml b/tenets/codelingo/php/shorthand-assignment-operator/codelingo.yaml new file mode 100644 index 00000000..ed8b1853 --- /dev/null +++ b/tenets/codelingo/php/shorthand-assignment-operator/codelingo.yaml @@ -0,0 +1,60 @@ +funcs: + - name: replaceAssignment + type: resolver + body: | + function(expr, varName) { + varName = '$' + varName; + expr = expr.split(' ').join(''); + operatorPos = expr.lastIndexOf(varName) + varName.length; + operator = expr[operatorPos]; + secondOperand = expr.substring(operatorPos+1); + if((operator == "+" || operator == "-") && secondOperand == 1){ + return varName + operator + operator; + } + return varName + " "+ operator + "= " + secondOperand; + } + + +tenets: + - name: shorthand-assignment-operator + actions: + codelingo/docs: + title: Shorthand Assignment Operators + body: It is recommended to use shorthand assignment operators + codelingo/review: + comment: It is recommended to use shorthand assignment operators + codelingo/rewrite: + query: | + import codelingo/ast/php + + @rewrite --replace "{{replaceAssignment(rawExpr, varName)}}" + @review comment + php.expr_assign(depth = any): + raw as rawExpr + php.expr_variable: + name as varName + any_of: + php.expr_binaryop_plus: + php.expr_variable: + sibling_order == 0 + name == varName + php.expr_binaryop_minus: + php.expr_variable: + sibling_order == 0 + name == varName + php.expr_binaryop_mul: + php.expr_variable: + sibling_order == 0 + name == varName + php.expr_binaryop_div: + php.expr_variable: + sibling_order == 0 + name == varName + php.expr_binaryop_mod: + php.expr_variable: + sibling_order == 0 + name == varName + php.expr_binaryop_concat: + php.expr_variable: + sibling_order == 0 + name == varName \ No newline at end of file diff --git a/tenets/codelingo/php/shorthand-assignment-operator/expected.json b/tenets/codelingo/php/shorthand-assignment-operator/expected.json new file mode 100644 index 00000000..3ed4a5a6 --- /dev/null +++ b/tenets/codelingo/php/shorthand-assignment-operator/expected.json @@ -0,0 +1,56 @@ +[ + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 6, + "Snippet": "$a = $a + 5;\n$a = $a * 2;\n$a = $a / 3;\n$a = $a % 4;\n$a= $a + 1;" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 13, + "Snippet": "$a = $a + $b;\n$a = \"This is \";\n$a = $a . \"a test\";\n\n" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 7, + "Snippet": "$a = $a * 2;\n$a = $a / 3;\n$a = $a % 4;\n$a= $a + 1;\n$a = $a - 1;" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 5, + "Snippet": "$a = $a + 5;\n$a = $a + 5;\n$a = $a * 2;\n$a = $a / 3;\n$a = $a % 4;" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 11, + "Snippet": "$a = $a - 1;\n$b = 6;\n$a = $a + $b;\n$a = \"This is \";\n$a = $a . \"a test\";" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 3, + "Snippet": "\u003c?php\n$a = 1;\n$a = $a + 5;\n$a = $a + 5;\n$a = $a * 2;" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 4, + "Snippet": "$a = 1;\n$a = $a + 5;\n$a = $a + 5;\n$a = $a * 2;\n$a = $a / 3;" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 8, + "Snippet": "$a = $a / 3;\n$a = $a % 4;\n$a= $a + 1;\n$a = $a - 1;\n$b = 6;" + }, + { + "Comment": "It is recommended to use shorthand assignment operators", + "Filename": "test.php", + "Line": 9, + "Snippet": "$a = $a % 4;\n$a= $a + 1;\n$a = $a - 1;\n$b = 6;\n$a = $a + $b;" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/php/shorthand-assignment-operator/test.php b/tenets/codelingo/php/shorthand-assignment-operator/test.php new file mode 100644 index 00000000..96b0d958 --- /dev/null +++ b/tenets/codelingo/php/shorthand-assignment-operator/test.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/tenets/codelingo/php/sql-concats/ideal-solution.lingo b/tenets/codelingo/php/sql-concats/ideal-solution.lingo deleted file mode 100644 index f9139269..00000000 --- a/tenets/codelingo/php/sql-concats/ideal-solution.lingo +++ /dev/null @@ -1,125 +0,0 @@ -tenets: - - name: sql-concats - doc: | - Find all http handlers that trigger a call to the database with unvalidated input. - - Steps: - - Find a value extracted from an http request. - - Follow the reaches graph (https://en.wikipedia.org/wiki/Reaching_definition) to function calls. - - Stop at variables defined by a call to the sanatise function. - - Recursively: - - Traverse to the function definition. - - Follow the reaches graph from the arguments to function calls. - Stop at variables defined by a call to the sanatise function. - - Conclude at the db function. - - Caveats: - - Currently only follows method calls, rather than methods and functions. - - Starts from all arguments of a function, rather than the argument passed in - - Gets false positives if sanatise is run off the call path. In other words, we recurse along a linear call chain from the handler to the db, if the input is sanatised by some other function it will be considered unsafe where following "reaches". - - Doesn't catch direct calls to the database from the handler. - - Possible reaches/defines issues: - - This tenet follows a direct reaching definition chain. This may be be confounded by having multiple references to the same variable, especially across function boundaries. - In this example b->val is only indirectly given an unsafe value, and the $userInput definition - may not recursively reach $b->val: - ```val = "safe"; - $a->BChild = $b; - $a->BChild->val = $userInput; - - mustBeCalledSafely($b->val);``` - - Variables can be influenced through non-definition means: - ```aMethod(TRUE); + $this->test(); + } + + public function test() { + $this->aMethod(TRUE); + $this->called(); + } + + private function called() { + echo "called"; + } + + private function not_called1() { + echo "not called"; + } + + private function not_called2() { + echo "not called"; + } +} \ No newline at end of file diff --git a/tenets/codelingo/php/unused-private-functions/expected.json b/tenets/codelingo/php/unused-private-functions/expected.json new file mode 100644 index 00000000..763e9287 --- /dev/null +++ b/tenets/codelingo/php/unused-private-functions/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "There is no call to this private function \n", + "Filename": "nonstatic.php", + "Line": 19, + "Snippet": " }\n\n private function not_called1() {\n echo \"not called\";\n }\n\n private function not_called2() {" + }, + { + "Comment": "There is no call to this private function \n", + "Filename": "nonstatic.php", + "Line": 23, + "Snippet": " }\n\n private function not_called2() {\n echo \"not called\";\n }\n}\n" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/psr1/camel-case-method-name/README.md b/tenets/codelingo/psr1/camel-case-method-name/README.md new file mode 100644 index 00000000..3b612d29 --- /dev/null +++ b/tenets/codelingo/psr1/camel-case-method-name/README.md @@ -0,0 +1,3 @@ +# camel-case-method-name + +_by codelingo, part of their PSR-1 bundle_ diff --git a/tenets/codelingo/psr1/camel-case-method-name/codelingo.yaml b/tenets/codelingo/psr1/camel-case-method-name/codelingo.yaml new file mode 100644 index 00000000..b4a263f5 --- /dev/null +++ b/tenets/codelingo/psr1/camel-case-method-name/codelingo.yaml @@ -0,0 +1,48 @@ +funcs: + - name: isInvalid + type: asserter + body: | + function(methodName) { + if (methodName.indexOf("_") !== -1) { + return true + } + if (methodName.charAt(0) >= 'A' && methodName.charAt(0) <= 'Z') { + return true + } + return false + } + - name: fixName + type: resolver + body: | + function(methodName) { + if (methodName.indexOf("_") === -1) { + return methodName[0].toLowerCase() + methodName.substring(1) + } + methodName = methodName.toLowerCase() + var splitName = methodName.split("_") + var newName = [] + newName.push(splitName[0]) + for (var i = 1; i < splitName.length; i++) { + newName.push(splitName[i].charAt(0).toUpperCase() + splitName[i].substring(1)) + } + return newName.join('') + } +tenets: + - name: camel-case-method-name + actions: + codelingo/docs: + title: Camel Case Method Name + body: Method names MUST be declared in camelCase + codelingo/review: + comment: Method names MUST be declared in camelCase + codelingo/rewrite: + query: | + import codelingo/ast/php + + php.stmt_class(depth = any): + php.stmt_classmethod: + @rewrite --replace "{{fixName(methodName)}}" + @review comment + php.identifier: + name as methodName + isInvalid(methodName) diff --git a/tenets/codelingo/psr1/camel-case-method-name/example.php b/tenets/codelingo/psr1/camel-case-method-name/example.php new file mode 100644 index 00000000..5fd75e4d --- /dev/null +++ b/tenets/codelingo/psr1/camel-case-method-name/example.php @@ -0,0 +1,14 @@ + 10) { + print('Hello'); + } elseif ($thing > 5) { + print('World'); + } elseif ($thing > 2) { + print('Yo'); + } +} diff --git a/tenets/codelingo/psr2/elseif-not-else-if/example.php b/tenets/codelingo/psr2/elseif-not-else-if/example.php new file mode 100644 index 00000000..2473785d --- /dev/null +++ b/tenets/codelingo/psr2/elseif-not-else-if/example.php @@ -0,0 +1,13 @@ + 10) { + print('Hello'); + } else if ($thing > 5) { + print('World'); + } else if ($thing > 2) { + print('Yo'); + } +} diff --git a/tenets/codelingo/psr2/elseif-not-else-if/expected.json b/tenets/codelingo/psr2/elseif-not-else-if/expected.json new file mode 100644 index 00000000..181d908f --- /dev/null +++ b/tenets/codelingo/psr2/elseif-not-else-if/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "The keyword elseif SHOULD be used instead of else if so that all control keywords look like single words. Raw is else if ($thing \u003e 5) {\n print('World');\n } else if ($thing \u003e 2) {\n print('Yo');\n }", + "Filename": "example.php", + "Line": 8, + "Snippet": " if ($thing \u003e 10) {\n print('Hello');\n } else if ($thing \u003e 5) {\n print('World');\n } else if ($thing \u003e 2) {\n print('Yo');\n }\n}" + }, + { + "Comment": "The keyword elseif SHOULD be used instead of else if so that all control keywords look like single words. Raw is else if ($thing \u003e 2) {\n print('Yo');\n }", + "Filename": "example.php", + "Line": 10, + "Snippet": " } else if ($thing \u003e 5) {\n print('World');\n } else if ($thing \u003e 2) {\n print('Yo');\n }\n}" + } + ] \ No newline at end of file diff --git a/tenets/codelingo/psr2/lingo_bundle.yaml b/tenets/codelingo/psr2/lingo_bundle.yaml new file mode 100644 index 00000000..a6e1a3fc --- /dev/null +++ b/tenets/codelingo/psr2/lingo_bundle.yaml @@ -0,0 +1,9 @@ +description: Best practices for PHP from PSR-2. +version: 0.0.0 +tenets: +- elseif-not-else-if +- lower-case-constant-values +tags: +- php +- psr +- psr2 diff --git a/tenets/codelingo/psr2/lower-case-constant-values/.codelingoignore b/tenets/codelingo/psr2/lower-case-constant-values/.codelingoignore new file mode 100644 index 00000000..47782043 --- /dev/null +++ b/tenets/codelingo/psr2/lower-case-constant-values/.codelingoignore @@ -0,0 +1 @@ +orig.php diff --git a/tenets/codelingo/psr2/lower-case-constant-values/README.md b/tenets/codelingo/psr2/lower-case-constant-values/README.md new file mode 100644 index 00000000..6e1a29fd --- /dev/null +++ b/tenets/codelingo/psr2/lower-case-constant-values/README.md @@ -0,0 +1,3 @@ +# lower-case-constant-values + +_by codelingo, part of their PSR-1 bundle_ diff --git a/tenets/codelingo/psr2/lower-case-constant-values/codelingo.yaml b/tenets/codelingo/psr2/lower-case-constant-values/codelingo.yaml new file mode 100644 index 00000000..3a15c837 --- /dev/null +++ b/tenets/codelingo/psr2/lower-case-constant-values/codelingo.yaml @@ -0,0 +1,33 @@ +funcs: + - name: isIncorrectCase + type: asserter + body: | + function(varName) { + var constants = ['true', 'false', 'null'] + + if (constants.indexOf(varName.toLowerCase()) !== -1) { + return varName !== varName.toLowerCase() + } + return false + } + - name: fixCase + type: resolver + body: | + function(varName) { + return varName.toLowerCase() + } +tenets: + - name: lower-case-constant-values + actions: + codelingo/review: + comment: The PHP constants true, false, and null MUST be in lower case. + codelingo/rewrite: + query: | + import codelingo/ast/php + + php.expr_constfetch(depth = any): + @review comment + @rewrite --replace "{{fixCase(varName)}}" + php.name_fullyqualified: + name as varName + isIncorrectCase(varName) diff --git a/tenets/codelingo/psr2/lower-case-constant-values/example-orig.php b/tenets/codelingo/psr2/lower-case-constant-values/example-orig.php new file mode 100644 index 00000000..722b8c75 --- /dev/null +++ b/tenets/codelingo/psr2/lower-case-constant-values/example-orig.php @@ -0,0 +1,15 @@ + 0".format(n)) + elif n == 1: + raise DeprecationWarning("{0} will be removed in next update".format(n), n) + elif n == 2: + raise NotImplementedError("no handler for {0}".format(n)) + else: + raise MemoryError("not enough memory to handle > 2") + except ValueError as error: + print(error.args[0]) + break + except DeprecationWarning as error: + # Ok for now + print("Deprecation warning: {0}".format(error.args[0])) + return error.args[1] + except NotImplementedError: + # That's ok, try again + continue + except MemoryError as error: + # Very bad, abort + print(repr(error)) + sys.exit(1) + + +def incorrect1(): + while True: + try: + n = random.randint(0, 4) + if n <= 0: + raise ValueError("got {0}, expected > 0".format(n)) + elif n == 1: + raise DeprecationWarning("{0} will be removed in next update".format(n), n) + elif n == 2: + raise NotImplementedError("no handler for {0}".format(n)) + else: + raise MemoryError("not enough memory to handle > 2") + except Exception as error: # ISSUE + # Something went wrong, abort + print(error) + sys.exit(1) + + +def incorrect2(): + while True: + try: + n = random.randint(0, 4) + if n <= 0: + raise ValueError("got {0}, expected > 0".format(n)) + elif n == 1: + raise DeprecationWarning("{0} will be removed in next update".format(n), n) + elif n == 2: + raise NotImplementedError("no handler for {0}".format(n)) + else: + raise MemoryError("not enough memory to handle > 2") + except: # ISSUE + # Who knows what went wrong, abort + print("error occurred") + sys.exit(1) + + +def main(): + n = correct() + if n is not None: + print("Success from correct(), got {0}".format(n)) + + n = incorrect1() + if n is not None: + print("Success from incorrect1(), got {0}".format(n)) + + n = incorrect2() + if n is not None: + print("Success from incorrect2(), got {0}".format(n)) + + +main() diff --git a/tenets/codelingo/python/raise-generic-exceptions/README.md b/tenets/codelingo/python/raise-generic-exceptions/README.md new file mode 100644 index 00000000..b5037bd6 --- /dev/null +++ b/tenets/codelingo/python/raise-generic-exceptions/README.md @@ -0,0 +1,4 @@ +# raise-generic-exceptions + +_by codelingo, part of their python bundle_ + diff --git a/tenets/codelingo/python/raise-generic-exceptions/codelingo.yaml b/tenets/codelingo/python/raise-generic-exceptions/codelingo.yaml new file mode 100644 index 00000000..367ec125 --- /dev/null +++ b/tenets/codelingo/python/raise-generic-exceptions/codelingo.yaml @@ -0,0 +1,24 @@ +tenets: + - name: raised-generic-exceptions + actions: + codelingo/docs: + title: Raised Generic Exceptions + body: | + Find raised generic exceptions. + + Raised exceptions should be as specific as possible to describe the issue as each type can be caught and handled differently. + Raising generic exceptions requires them to be caught, which will also catch all other exception types lower in the hierarchy. + See the full hierarchy of the built in exceptions here: https://docs.python.org/3/library/exceptions.html#exception-hierarchy. + codelingo/review: + comment: | + Exception type is too generic. Use a more specific exception: https://docs.python.org/3/library/exceptions.html#exception-hierarchy. + query: | + import ( + codelingo/ast/python36 + ) + + python36.raise(depth = any): + @review comment + python36.call: + python36.name: + id == "Exception" diff --git a/tenets/codelingo/python/raise-generic-exceptions/example.py b/tenets/codelingo/python/raise-generic-exceptions/example.py new file mode 100644 index 00000000..13d42b21 --- /dev/null +++ b/tenets/codelingo/python/raise-generic-exceptions/example.py @@ -0,0 +1,21 @@ +def correct(): + raise NotImplementedError("correct() isn't implemented; but is still correct") + + +def incorrect(): + raise Exception("incorrect() isn't implemented; but is still incorrect") # ISSUE + + +def main(): + try: + correct() + except NotImplementedError as error: + print("Caught: " + repr(error)) + + try: + incorrect() # this will cause a crash + except NotImplementedError as error: + print("Caught: " + repr(error)) + + +main() diff --git a/tenets/codelingo/rfjakob-gocryptfs/lingo_bundle.yaml b/tenets/codelingo/rfjakob-gocryptfs/lingo_bundle.yaml new file mode 100644 index 00000000..84c0823b --- /dev/null +++ b/tenets/codelingo/rfjakob-gocryptfs/lingo_bundle.yaml @@ -0,0 +1,8 @@ +description: Tenets written for rfjakob-gocryptfs +version: 0.0.0 +tenets: +- missing-close-file +tags: +- go +- rfjakob +- gocryptfs diff --git a/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/codelingo.yaml b/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/codelingo.yaml new file mode 100644 index 00000000..19a994fa --- /dev/null +++ b/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/codelingo.yaml @@ -0,0 +1,29 @@ +tenets: + - name: missing-close-file + actions: + codelingo/review: + comment: Close the file when you are done + query: | + import codelingo/ast/go + + go.func_decl(depth = any): + @review comment + go.assign_stmt(depth = any): + go.lhs: + go.ident: + sibling_order == 0 + name as varName + go.rhs: + go.call_expr: + go.selector_expr: + go.ident: + name == "os" + go.ident: + name == "Open" + exclude: + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == varName + go.ident: + name == "Close" diff --git a/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/expected.json b/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/expected.json new file mode 100644 index 00000000..613a41cc --- /dev/null +++ b/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/expected.json @@ -0,0 +1,8 @@ +[ + { + "Comment": "Close the file when you are done", + "Filename": "test.go", + "Line": 16, + "Snippet": "func main() {\n\n f, err := os.Open(\"/tmp/test.txt\")\n check(err)\n b1 := make([]byte, 5)" + } +] \ No newline at end of file diff --git a/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/test.go b/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/test.go new file mode 100644 index 00000000..537ec989 --- /dev/null +++ b/tenets/codelingo/rfjakob-gocryptfs/missing-close-file/test.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + +func main() { + + f, err := os.Open("/tmp/test.txt") + check(err) + b1 := make([]byte, 5) + n1, err := f.Read(b1) + check(err) + fmt.Printf("%d bytes: %s\n", n1, string(b1)) + //f.Close() + +} diff --git a/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/codelingo.yaml b/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/codelingo.yaml new file mode 100644 index 00000000..2e1ab165 --- /dev/null +++ b/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/codelingo.yaml @@ -0,0 +1,72 @@ +funcs: + - name: isGreater + type: asserter + body: | + function(val1, val2) { + return val1 == (val2 + 2) + } + - name: isNextStmt + type: asserter + body: | + function(val1, val2) { + return val1 == (val2 + 1) + } + - name: isEqual + type: asserter + body: | + function(val1, val2) { + return val1 == val2 + } +tenets: + - name: find-sync-mutex-lock + actions: + codelingo/review: + comment: Replace sync/mutex `{{mtxName}}` with sync/atomic to improve performance + query: | + import codelingo/ast/go + + go.file(depth = any): + go.decls: + go.gen_decl: + go.value_spec: + go.names: + go.ident: + name as mtxName + go.selector_expr: + go.ident: + name == "sync" + go.ident: + name == "Mutex" + go.func_decl(depth = any): + go.block_stmt: + go.list: + child_count as ch + @review comment + go.expr_stmt(depth = any): + sibling_order as sb1 + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == mtxName + go.ident: + name == "Lock" + any_of: + go.expr_stmt(depth = any): + sibling_order as sb2 + isGreater(sb2, sb1) + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == mtxName + go.ident: + name == "Unlock" + go.defer_stmt(depth = any): + sibling_order as sb3 + isNextStmt(sb3, sb1) + isEqual(ch, 3) + go.call_expr(depth = any): + go.selector_expr: + go.ident: + name == mtxName + go.ident: + name == "Unlock" diff --git a/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/expected.json b/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/expected.json new file mode 100644 index 00000000..2dd7677c --- /dev/null +++ b/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/expected.json @@ -0,0 +1,14 @@ +[ + { + "Comment": "Replace sync/mutex `m` with sync/atomic to improve performance", + "Filename": "test.go", + "Line": 18, + "Snippet": "\nfunc test2(){\n\tm.Lock() //ISSUE\n\tdefer m.Unlock()\n\tt = true" + }, + { + "Comment": "Replace sync/mutex `m` with sync/atomic to improve performance", + "Filename": "test.go", + "Line": 12, + "Snippet": "}\nfunc test1(){\n\tm.Lock() //ISSUE\n\tt = false\n\tm.Unlock()" + } +] \ No newline at end of file diff --git a/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/test.go b/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/test.go new file mode 100644 index 00000000..bfcf2a99 --- /dev/null +++ b/tenets/codelingo/thrasher-gocryptotrader/find-sync-mutex-lock/test.go @@ -0,0 +1,26 @@ +package main + +import ( + "sync" +) +var m sync.Mutex +var t = false +type test struct{ + b bool +} +func test1(){ + m.Lock() //ISSUE + t = false + m.Unlock() +} + +func test2(){ + m.Lock() //ISSUE + defer m.Unlock() + t = true +} + +func main() { + go test1() + go test2() +} diff --git a/tenets/codelingo/thrasher-gocryptotrader/lingo_bundle.yaml b/tenets/codelingo/thrasher-gocryptotrader/lingo_bundle.yaml new file mode 100644 index 00000000..ad5d91e1 --- /dev/null +++ b/tenets/codelingo/thrasher-gocryptotrader/lingo_bundle.yaml @@ -0,0 +1,8 @@ +description: Tenets written for thrasher-/gocryptotrader +version: 0.0.0 +tenets: +- find-sync-mutex-lock +tags: +- go +- thrasher +- gocryptotrader \ No newline at end of file diff --git a/tenets/codelingo/west/allocation/codelingo.yaml b/tenets/codelingo/west/allocation/codelingo.yaml index 1383f894..44040a31 100644 --- a/tenets/codelingo/west/allocation/codelingo.yaml +++ b/tenets/codelingo/west/allocation/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: freed-new-object - flows: + actions: codelingo/docs: title: Freed New Object body: Finds objects that were created with new and freed. @@ -24,7 +24,7 @@ tenets: identifier_token as varName - name: deleted-malloced-objects - flows: + actions: codelingo/docs: title: Deleted Malloced Objects body: Finds objects that were obtained with malloc and deleted. @@ -50,7 +50,7 @@ tenets: identifier_token as varName - name: used-after-deletion - flows: + actions: codelingo/docs: title: Used After Deletion body: Finds objects used after deletion. @@ -73,7 +73,7 @@ tenets: identifier_token as varName - name: undeleted-returned-object - flows: + actions: codelingo/docs: title: Undeleted Returned Object body: Finds objects new that are not deleted. diff --git a/tenets/codelingo/west/lingo_bundle.yaml b/tenets/codelingo/west/lingo_bundle.yaml index eb18ef25..0af72822 100644 --- a/tenets/codelingo/west/lingo_bundle.yaml +++ b/tenets/codelingo/west/lingo_bundle.yaml @@ -1,11 +1,11 @@ description: Tenets written for West version: 0.0.0 tenets: - - allocation - - shadowing - - switch +- allocation +- shadowing +- switch tags: - - c# - - csharp - - cpp - - c++ \ No newline at end of file +- c# +- csharp +- cpp +- c++ diff --git a/tenets/codelingo/west/shadowing/codelingo.yaml b/tenets/codelingo/west/shadowing/codelingo.yaml index b9ca47ad..6b4fe025 100644 --- a/tenets/codelingo/west/shadowing/codelingo.yaml +++ b/tenets/codelingo/west/shadowing/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: shadowed-parameter - flows: + actions: codelingo/docs: title: Shadowed Parameter body: Finds parameters that are updated in the method body. @@ -19,7 +19,7 @@ tenets: name as paramName - name: shadowed-declaration - flows: + actions: codelingo/docs: title: Shadowed Declaration body: Finds declarations that are updated in an inner scope. diff --git a/tenets/codelingo/west/switch/codelingo.yaml b/tenets/codelingo/west/switch/codelingo.yaml index 38c427a7..3a07c9a6 100644 --- a/tenets/codelingo/west/switch/codelingo.yaml +++ b/tenets/codelingo/west/switch/codelingo.yaml @@ -1,6 +1,6 @@ tenets: - name: fall-through-case - flows: + actions: codelingo/docs: title: Fall Through Case body: Finds non-empty cases that fall through. diff --git a/util/check/rule.go b/util/check/rule.go new file mode 100644 index 00000000..0fbfb578 --- /dev/null +++ b/util/check/rule.go @@ -0,0 +1,159 @@ +package check + +import ( + "encoding/json" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "sort" + "time" + + "github.com/codelingo/clql/dotlingo" + "github.com/juju/errors" + "github.com/juju/testing/checkers" +) + +var ( + ErrNoRule = errors.New("not a rule") + ErrBadRule = errors.New("rule not valid") + ErrNoTests = errors.New("has no tests") + ErrFailed = errors.New("test failed") +) + +// TestRule returns an error unless the supplied path contains a valid +// codelingo rule with tests that pass. Specific conditions will be signalled +// by returning errors with a Cause of ErrNoRule, ErrBadRule, ErrNoTests, or +// ErrFailed; no particular inferences can be drawn from other non-nil errors. +func TestRule(dir string) error { + expect, err := CheckRule(dir) + if err != nil { + return err + } + actual, err := RunReview(dir) + if err != nil { + return err + } + if _, err := checkers.DeepEqual(actual, expect); err != nil { + return errors.Wrap(err, ErrFailed) + } + return nil +} + +// CheckRule returns the supplied rule directory's expected test results, or an +// error. It will return ErrNoRule if the codelingo file is missing; ErrBadRule +// if the codelingo file cannot be parsed; and ErrNoTests if the expected.json +// file is missing. +func CheckRule(dir string) ([]Issue, error) { + path := filepath.Join(dir, "codelingo.yaml") + content, err := ioutil.ReadFile(path) + if os.IsNotExist(err) { + path = filepath.Join(dir, "codelingo.yml") + content, err = ioutil.ReadFile(path) + } + if os.IsNotExist(err) { + return nil, ErrNoRule + } else if err != nil { + return nil, errors.Annotatef(err, "cannot read rule file") + } + if _, err := dotlingo.Parse(string(content)); err != nil { + return nil, errors.Wrap(err, ErrBadRule) + } + + expect, err := ReadIssues(filepath.Join(dir, "expected.json")) + if os.IsNotExist(errors.Cause(err)) { + return nil, ErrNoTests + } else if err != nil { + return nil, errors.Annotatef(err, "cannot read expected results") + } + return expect, nil +} + +// RunReview runs `lingo run review` in a temporary git repository copied from dir. +func RunReview(dir string) ([]Issue, error) { + datePrefix := time.Now().Format("2006-01-02-") + tempDir, err := ioutil.TempDir("", datePrefix) + if err != nil { + return nil, errors.Annotatef(err, "cannot create work dir") + } + defer os.RemoveAll(tempDir) + + copyDir := filepath.Join(tempDir, "rule") + if _, err := Run("", "cp", "-r", dir, copyDir); err != nil { + return nil, errors.Annotatef(err, "cannot copy rule dir") + } + if err := Script(copyDir, [][]string{ + {"git", "init"}, + {"git", "add", "."}, + {"git", "commit", "-m", "for testing"}, + {"lingo", "run", "review", "--keep-all", "-o", "../actual.json"}, + }); err != nil { + return nil, errors.Annotatef(err, "cannot setup/run lingo review") + } + issues, err := ReadIssues(filepath.Join(tempDir, "actual.json")) + if os.IsNotExist(errors.Cause(err)) { + // lingo doesn't bother to write output if it found nothing, pretend we + // found an empty list instead for consistency's sake. + return nil, nil + } else if err != nil { + return nil, errors.Annotatef(err, "cannot read actual results") + } + return issues, nil +} + +// Run invokes a command in a directory, and returns its CombinedOutput. +func Run(dir, name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + return "", errors.Annotatef(err, "cannot run; output: %s", out) + } + return string(out), nil +} + +// Script invokes a sequence of commands, until one fails. +func Script(dir string, cmds [][]string) error { + for _, cmd := range cmds { + name, args := cmd[0], cmd[1:] + if _, err := Run(dir, name, args...); err != nil { + return errors.Trace(err) + } + } + return nil +} + +// ReadIssues reads a file containing a json-encoded list of Issues, and +// returns them sorted by location. +func ReadIssues(path string) ([]Issue, error) { + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, errors.Annotatef(err, "cannot read file") + } + var issues []Issue + if err := json.Unmarshal(content, &issues); err != nil { + return nil, errors.Annotatef(err, "cannot unmarshal content") + } + sort.Sort(byLocation(issues)) + return issues, nil +} + +// Issue matches the structure of lingo review results. +type Issue struct { + Comment string + Filename string + Line int + Snippet string +} + +// byLocation sorts Issues by file then line. +type byLocation []Issue + +func (b byLocation) Len() int { return len(b) } +func (b byLocation) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byLocation) Less(i, j int) bool { + if b[i].Filename < b[j].Filename { + return true + } + return b[i].Line < b[j].Line +} diff --git a/util/cmd/check/README.md b/util/cmd/check/README.md new file mode 100644 index 00000000..8127ede2 --- /dev/null +++ b/util/cmd/check/README.md @@ -0,0 +1,17 @@ +Rule testing utility +==================== + +Accepts any number of non-flag arguments; if none are supplied, acts as though +called with "." alone. + +If no flag is set, tests every supplied rule dir. + +If the "--search" flag is set, scans each supplied root dir for rule dirs and +tests all rules found. + +Examples +-------- + + $ go run ./main.go some/rule/dir another/rule/dir + + $ go run ./main.go --search some/root/dir another/root/dir diff --git a/util/cmd/check/main.go b/util/cmd/check/main.go new file mode 100644 index 00000000..941934fd --- /dev/null +++ b/util/cmd/check/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "time" + + "github.com/juju/errors" + + "github.com/codelingo/codelingo/util/check" +) + +func main() { + var r results + for _, relPath := range targets(os.Args[1:]) { + fmt.Printf("-> %s ... ", relPath) + + // Check a (potential) rule directory. + start := time.Now() + absPath, err := filepath.Abs(relPath) + if err == nil { + err = check.TestRule(absPath) + } + elapsed := getElapsed(start) + r.Add(err) + + // Show outcome inline. + outcome := "???" + detail := "" + cause := errors.Cause(err) + switch cause { + case nil: + outcome = "ok" + case check.ErrFailed, check.ErrBadRule: + // This is a hack, and depends on TestRule not tracing/annotating. + detail = err.(*errors.Err).Underlying().Error() + outcome = "FAIL" + case check.ErrNoRule, check.ErrNoTests: + outcome = "SKIP" + default: + detail = errors.ErrorStack(err) + outcome = "ERROR" + } + fmt.Println(outcome, elapsed) + if detail != "" { + fmt.Println(detail) + } + } + + // Show summary. + fmt.Println(r) + if r.Count() != r.Success { + os.Exit(1) + } +} + +// targets converts os Args into a list of directories to inspect. +func targets(args []string) []string { + search := false + var filtered []string + for _, arg := range args { + if arg == "--search" { + search = true + } else { + filtered = append(filtered, arg) + } + } + if len(filtered) == 0 { + filtered = []string{"."} + } + if !search { + return filtered + } + return find(filtered) +} + +// find returns all directories containing codelingo files under all supplied roots. +func find(roots []string) []string { + found := map[string]bool{} + check := func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if info.IsDir() { + return nil + } + switch base := filepath.Base(path); base { + case "codelingo.yaml", "codelingo.yml": + found[filepath.Dir(path)] = true + return filepath.SkipDir + } + return nil + } + for _, root := range roots { + filepath.Walk(root, check) + } + result := make([]string, 0, len(found)) + for path := range found { + result = append(result, path) + } + sort.Strings(result) + return result +} + +// getElapsed formats time usefully for output. +func getElapsed(start time.Time) string { + return fmt.Sprintf("(%.3fs)", time.Since(start).Seconds()) +} + +// results collects outcomes for summarizing later. +type results struct { + Success int + Failure int + Invalid int + Untested int + Error int +} + +func (r *results) Add(err error) { + switch cause := errors.Cause(err); cause { + case nil: + r.Success++ + case check.ErrFailed: + r.Failure++ + case check.ErrNoRule, check.ErrBadRule: + r.Invalid++ + case check.ErrNoTests: + r.Untested++ + default: + r.Error++ + } +} + +func (r results) Count() int { + return r.Success + r.Failure + r.Invalid + r.Untested + r.Error +} + +func (r results) String() string { + return fmt.Sprintf(` +Inspected %d rule directories. +%d passed tests. +%d failed tests. +%d were invalid. +%d had no tests. +%d went wrong in some other way. +`, r.Count(), r.Success, r.Failure, r.Invalid, r.Untested, r.Error) +} diff --git a/util/registry/main.go b/util/registry/main.go new file mode 100644 index 00000000..d9ca68cb --- /dev/null +++ b/util/registry/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path" + + "gopkg.in/yaml.v1" +) + +func main() { + actualRules := getActualRules() + statedRules := getStatedRules() + missingActualRules := []string{} + missingStatedRules := []string{} + + for rule := range actualRules { + if _, ok := statedRules[rule]; !ok { + missingStatedRules = append(missingStatedRules, rule) + } + } + + for rule := range statedRules { + if _, ok := actualRules[rule]; !ok { + missingActualRules = append(missingActualRules, rule) + } + } + + fmt.Println("Missing Stated Rules: ", missingStatedRules) + fmt.Println("Missing Actual Rules: ", missingActualRules) +} + +func getStatedRules() map[string]bool { + rules := map[string]bool{} + gopath := os.Getenv("GOPATH") + registryPath := path.Join(gopath, "src/github.com/codelingo/codelingo/registry/tenets.yaml") + + registryContents, err := ioutil.ReadFile(registryPath) + if err != nil { + panic(err.Error()) + } + + r := map[interface{}]map[interface{}]map[interface{}]map[interface{}]interface{}{} + yaml.Unmarshal(registryContents, &r) + for o, bundles := range r { + owner := o.(string) + for b, contents := range bundles { + bundle := b.(string) + for key, ruleList := range contents { + k := key.(string) + if k != "tenets" { + continue + } + for r := range ruleList { + rule := r.(string) + rules[path.Join(owner, bundle, rule)] = true + } + } + } + } + return rules +} + +func getActualRules() map[string]bool { + gopath := os.Getenv("GOPATH") + rulesPath := path.Join(gopath, "src/github.com/codelingo/codelingo/tenets") + + rules := map[string]bool{} + + owners, err := ioutil.ReadDir(rulesPath) + if err != nil { + panic(err.Error()) + } + + for _, owner := range owners { + if owner.IsDir() { + bundles, err := ioutil.ReadDir(path.Join(rulesPath, owner.Name())) + if err != nil { + panic(err.Error()) + } + + for _, bundle := range bundles { + if bundle.IsDir() { + ruleDirs, err := ioutil.ReadDir(path.Join(rulesPath, owner.Name(), bundle.Name())) + if err != nil { + panic(err.Error()) + } + + for _, rule := range ruleDirs { + if rule.IsDir() { + rules[path.Join(owner.Name(), bundle.Name(), rule.Name())] = true + } + } + } + } + + } + } + + return rules +}